Este tutorial adapta pedagógicamente el Día 81. Los archivos suministrados del curso son solo dos: los objetivos (lección 597) y los puntos de aprendizaje (lección 598); según el curso, "todos los retos y explicaciones de hoy están en el cuaderno", que viene en
Multivariable Regression (Start).zipy no forma parte de los archivos suministrados. Para no dejar el día vacío, esta adaptación consultó una copia pública del cuaderno de inicio (publicada en GitHub por otro estudiante del mismo curso): sus retos se resumen en las secciones 📓 Reto del cuaderno, identificados como tales. Todas las soluciones y explicaciones son de esta adaptación y se verificaron con los datos. Los retos 🟢🟡🟠🔴, la sección de ética de los datos, el proyecto extra y el proyecto integrador final de los Días 76 a 81 son creaciones de esta adaptación. Las correcciones aparecen como 🛠️ Nota técnica.
Nota sobre las verificaciones: el 27 de septiembre de 2026, con Python 3.14, pandas 3.0.6, NumPy 2.5.3, seaborn 0.13.2, plotly 7.1.0, scikit-learn 1.9.1 y SciPy 1.18.1, y con una copia pública de
boston.csv(506 filas y 14 columnas), se ejecutó todo el código. Contrain_test_split(test_size=0.2, random_state=10): r² de 0,75 (entrenamiento) y 0,67 (prueba) con los precios originales, y de 0,79 y 0,74 con el logaritmo del precio; la asimetría (skew) del precio baja de 1,11 a −0,33 con el logaritmo. También se verificó que scikit-learn eliminó este conjunto de datos en la versión 1.2 (load_bostonya no existe) por un problema ético con una de sus columnas (sección 9).
Tabla de contenidos
- Antes de empezar: tasador de viviendas en Boston, 1970
- Explorar los datos
- Visualizar las variables una por una
- Relaciones entre variables:
pairplotyjointplot - Datos de entrenamiento y de prueba
- Regresión multivariable y sus coeficientes
- Analizar los residuos
- Transformar los datos: el logaritmo del precio
- Probar el modelo y valorar una propiedad
- 📖 Ética de los datos: la columna B
- 🚀 Proyecto del día: el tasador
- 🌟 Proyecto extra: ¿cuánto vale un celular usado?
- 🏆 Proyecto integrador final: Laboratorio de Datos Abiertos
- 🧩 Comprueba lo aprendido
- 🤖 Programar con apoyo de IA
- 🛠️ Correcciones técnicas al material original
- 📋 Matriz de trazabilidad
- 📦 Evidencias de aprendizaje del Día 81
0. Antes de empezar: tasador de viviendas en Boston, 1970
(Fuente: 597-day-81-goals-what-you-will-make-by-the-end-of-the-day.md)
📝 Contenido original del curso
¡Bienvenido a Boston, Massachusetts, en los años 70! Trabajas para una empresa de desarrollo inmobiliario que quiere valorar cualquier proyecto residencial antes de empezarlo. Tu tarea: construir un modelo que estime el precio de una vivienda a partir de características como:
- el número de habitaciones;
- la distancia a los centros de empleo;
- qué tan rica o pobre es la zona;
- cuántos estudiantes hay por docente en los colegios de la zona, etc.
Hoy vas a:
- Analizar y explorar los datos de precios de vivienda de Boston.
- Dividir los datos en entrenamiento y prueba.
- Ejecutar una regresión multivariable.
- Evaluar los coeficientes y los residuos del modelo.
- Usar una transformación de los datos para mejorar el modelo.
- Usar el modelo para estimar el precio de una propiedad.
Preparación: descarga el ZIP, sube el cuaderno a Google Drive y ábrelo con Colab; agrega el CSV. Todos los retos y explicaciones de hoy están en el cuaderno.
Prerequisitos: regresión lineal simple, r² e intercepto (Día 78); histogramas, KDE y asimetría (Días 79 y 80);
np.logynp.exp(Día 77). Este es "el proyecto de ciencia de datos más desafiante hasta ahora".
🧠 ¿Qué aprenderemos?
| Día 78 | Día 81 |
|---|---|
| 1 variable explicativa (presupuesto) | 13 variables explicativas |
| Se evalúa con los mismos datos del ajuste | Se evalúa con datos que el modelo no vio |
| r² y gráfico | r², signos de los coeficientes, residuos |
| Datos tal cual | Datos transformados (logaritmo) |
1. Explorar los datos
(Contenido del cuaderno del curso, no incluido en los archivos suministrados.)
📓 Reto del cuaderno
¿Forma de data?, ¿filas y columnas?, ¿nombres?, ¿NaN o duplicados? Después, con describe(): ¿cuántos estudiantes hay por docente en promedio?, ¿precio promedio?, ¿qué es CHAS y por qué su mínimo es 0 y su máximo 1?, ¿mínimo y máximo de habitaciones?
El cuaderno describe las columnas: CRIM (tasa de criminalidad), ZN (terreno residencial para lotes grandes), INDUS (proporción de industria no minorista), CHAS (1 si limita con el río Charles), NOX (óxidos de nitrógeno: contaminación), RM (habitaciones promedio por vivienda), AGE (viviendas anteriores a 1940), DIS (distancia a 5 centros de empleo), RAD (acceso a autopistas), TAX (impuesto predial), PTRATIO (estudiantes por docente), B (una variable construida a partir de la proporción de población negra; sección 9), LSTAT (% de población de "estatus bajo") y PRICE (valor mediano de las viviendas, en miles de dólares).
📖 Solución y explicación (verificada)
import pandas as pd
data = pd.read_csv('boston.csv', index_col=0) # la 1.ª columna es el número de fila
data.shape # (506, 14)
data.isna().values.any() # False
data.duplicated().values.any() # False
data.describe()| Pregunta | Respuesta verificada |
|---|---|
| Estudiantes por docente (promedio) | 18,46 |
| Precio promedio | 22,53 (es decir, 22 530 dólares de los años 70) |
CHAS |
Variable indicadora (dummy): 1 = junto al río, 0 = no. Por eso solo toma esos valores |
| Habitaciones (mínimo / máximo) | 3,56 / 8,78 (son promedios por zona, por eso tienen decimales) |
💡 ¿Qué es cada fila? No es una casa: es una zona censal de Boston.
PRICEes el valor mediano de las viviendas de esa zona yRM, el promedio de habitaciones. Todo el análisis habla de zonas, no de casas individuales.
🛠️ Nota técnica (verificado): 16 zonas tienen
PRICE = 50,0exactamente, el máximo. Es probable que los valores se hayan limitado (censurado) a 50: una zona "de 50" podría valer más. Es la misma señal que el límite de 100 MB del Día 76: muchos datos amontonados en el máximo.
2. Visualizar las variables una por una
(Contenido del cuaderno del curso, no incluido en los archivos suministrados.)
📓 Reto del cuaderno
Con sns.displot() (histograma con KDE, aspect=2) grafica PRICE, RM, DIS y RAD. ¿Qué notas en sus distribuciones? Luego, un gráfico de barras de plotly para CHAS que muestre cuántas más viviendas están lejos del río (pista: x=['No', 'Yes']).
📖 Solución y explicación (verificada)
import seaborn as sns
import plotly.express as px
sns.displot(data, x='PRICE', kde=True, aspect=2, bins=50)
plt.title('Distribución de los precios (miles de USD)')
plt.show()
chas = data.CHAS.value_counts() # 0.0: 471, 1.0: 35
bar = px.bar(x=['No', 'Sí'], y=chas.values, color=chas.values,
title='¿Junto al río Charles?')
bar.update_layout(xaxis_title='¿La zona limita con el río?', yaxis_title='Número de zonas',
coloraxis_showscale=False)
bar.show()Qué se observa:
PRICE: sesgado a la derecha, con un "pico" artificial en 50 (nota técnica anterior).RM: bastante simétrico, alrededor de 6,3.DIS: sesgado a la derecha: la mayoría de zonas está cerca de los centros de empleo.RAD: dos grupos muy separados (muchas zonas con valores bajos y un bloque en 24): es un índice, no una distancia.
⚠️ Cuidado con el orden de
value_counts():x=['No', 'Sí']solo es correcto si el primer valor contado es 0. Verificado: aquí el orden es[0.0, 1.0], porque hay más ceros. Es más seguro construir las etiquetas a partir del índice:x=chas.index.map({0: 'No', 1: 'Sí'}).
displot es una función de "figura" (como lmplot, Día 79): crea su propia figura, así que plt.figure(dpi=...) antes de ella no tiene efecto; el tamaño se controla con height y aspect.
3. Relaciones entre variables: pairplot y jointplot
(Contenido del cuaderno del curso, no incluido en los archivos suministrados; la lección 598 menciona .pairplot().)
📝 Contenido original del curso (lección 598)
Aprendiste a detectar rápidamente relaciones en un conjunto de datos con .pairplot() de seaborn.
📓 Reto del cuaderno
Antes de ejecutar, predice: ¿cómo se relacionan la contaminación (NOX) y la distancia al empleo (DIS)? ¿Las habitaciones (RM) y el precio? ¿La pobreza (LSTAT) y el precio? Ejecuta sns.pairplot(data) (tarda 1–2 minutos) y comprueba. Luego, sns.jointplot() con transparencia para: DIS–NOX, INDUS–NOX, LSTAT–RM, LSTAT–PRICE y RM–PRICE.
📖 Solución y explicación (verificada)
sns.pairplot(data[['PRICE', 'RM', 'LSTAT', 'NOX', 'DIS']]) # un subconjunto es más rápido y legible
with sns.axes_style('darkgrid'):
sns.jointplot(data=data, x='DIS', y='NOX', height=8, joint_kws={'alpha': 0.5})
plt.show()Un pairplot dibuja todas las parejas de variables (con 14 columnas, 196 gráficos); un jointplot muestra una pareja en detalle, con el histograma de cada variable en los bordes.
| Pareja | Correlación verificada | Lectura |
|---|---|---|
LSTAT–PRICE |
−0,74 | Más pobreza en la zona → precios más bajos |
RM–PRICE |
+0,70 | Más habitaciones → precios más altos |
DIS–NOX |
−0,77 | Lejos de los centros de empleo (e industria) → menos contaminación |
INDUS–NOX |
+0,76 | Más industria → más contaminación |
LSTAT–RM |
−0,61 | Zonas más pobres → viviendas con menos habitaciones |
💡 Predecir antes de ejecutar es una buena práctica científica: si el gráfico te sorprende, aprendiste algo; si no escribiste tu predicción, es fácil convencerte de que "ya lo sabías".
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: calcula la matriz de correlaciones y grafícala con
sns.heatmap(data.corr(), annot=True). - 🟡 Reto 2: ¿qué dos variables explicativas están más correlacionadas entre sí? ¿Qué problema podría causar eso en la regresión? (Busca "multicolinealidad".)
4. Datos de entrenamiento y de prueba
(Contenido del cuaderno del curso; la lección 598 lo resume.)
📝 Contenido original del curso (lección 598)
Aprendiste a dividir los datos en un conjunto de entrenamiento y otro de prueba para evaluar mejor el rendimiento del modelo.
📓 Reto del cuaderno
No podemos usar las 506 filas para entrenar: queremos evaluar el modelo con datos que no ha visto (fuera de la muestra). Importa train_test_split() (se abre en otra pestaña), crea X_train, X_test, y_train, y_test, divide aproximadamente 80/20 y usa random_state=10 para obtener siempre la misma división. El objetivo es PRICE; las características, las demás columnas.
📖 Solución y explicación (verificada)
from sklearn.model_selection import train_test_split
X = data.drop('PRICE', axis=1)
y = data['PRICE']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=10)
len(X_train), len(X_test) # (404, 102)¿Por qué dividir? Es como estudiar para un examen: si el profesor pone exactamente los ejercicios que resolviste, sacar 5,0 no demuestra que aprendiste; podrías haberlos memorizado. El conjunto de prueba es el "examen con ejercicios nuevos". Un modelo que va muy bien en entrenamiento y mal en prueba está sobreajustado (memorizó en lugar de aprender).
random_state: la división es aleatoria; fijar la semilla hace que todos obtengan las mismas 404 y 102 filas y, por tanto, los mismos resultados.
⚠️ Errores frecuentes
- Olvidar quitar
PRICEde X: el modelo "hace trampa" y obtiene r² = 1. - Desempacar en otro orden: el orden es
X_train, X_test, y_train, y_test; cambiarlo mezcla características y precios sin error visible. - Mirar el conjunto de prueba para tomar decisiones (elegir variables, transformar): entonces deja de ser "datos nuevos".
5. Regresión multivariable y sus coeficientes
(Contenido del cuaderno del curso; la lección 598 lo resume.)
📝 Contenido original del curso (lección 598)
Aprendiste a ejecutar una regresión multivariable y a evaluarla según el signo de sus coeficientes.
📓 Retos del cuaderno
El modelo tiene la forma:
PRECIO_estimado = θ0 + θ1·RM + θ2·NOX + θ3·DIS + θ4·CHAS + ... + θ13·LSTAT- Ejecuta la regresión con los datos de entrenamiento. ¿Qué r² obtienes?
- Imprime los coeficientes en un DataFrame. ¿
RMes positivo? ¿Qué signo tieneLSTAT? ¿Los demás tienen el signo esperado? Según el modelo, ¿cuánto más cuesta una vivienda con una habitación más?
📖 Solución y explicación (verificada)
from sklearn.linear_model import LinearRegression
regr = LinearRegression()
regr.fit(X_train, y_train)
regr.score(X_train, y_train) # 0.750
coeficientes = pd.DataFrame(data=regr.coef_, index=X_train.columns, columns=['Coeficiente'])| Variable | Coeficiente | ¿Signo esperado? |
|---|---|---|
RM |
+3,108 | ✅ Más habitaciones, más caro |
LSTAT |
−0,582 | ✅ Más pobreza, más barato |
NOX |
−16,272 | ✅ Más contaminación, más barato |
CHAS |
+1,975 | ✅ Junto al río, más caro |
PTRATIO |
−0,820 | ✅ Más estudiantes por docente, más barato |
CRIM |
−0,128 | ✅ Más criminalidad, más barato |
DIS |
−1,483 | 🤔 ¿Más lejos del empleo, más barato? Depende de lo que pase con las demás variables |
AGE |
+0,016 | 🤔 Casi cero |
Una habitación más: el coeficiente de RM es 3,108 (miles de dólares): unos 3 108 dólares más, manteniendo iguales las demás variables.
La idea clave de la regresión multivariable: cada coeficiente mide el efecto de su variable cuando las otras 12 no cambian. Por eso un coeficiente puede tener un signo "raro" (como DIS): esa variable está muy relacionada con otras (NOX, INDUS) y el modelo reparte el efecto entre ellas.
⚠️ Los tamaños de los coeficientes no se comparan directamente.
NOX(−16,3) no es "más importante" queRM(3,1):NOXvaría entre 0,38 y 0,87, mientras queRMvaría entre 3,6 y 8,8. Cada coeficiente está en las unidades de su variable.
⚠️ Errores frecuentes
- Interpretar un coeficiente sin "manteniendo lo demás constante".
- Asociar coeficientes y nombres a mano:
pd.DataFrame(regr.coef_, index=X_train.columns)evita desordenarlos. - Olvidar las unidades:
PRICEestá en miles; el coeficiente también.
6. Analizar los residuos
(Contenido del cuaderno del curso; la lección 598 lo resume.)
📝 Contenido original del curso (lección 598)
Aprendiste a analizar y buscar patrones en los residuos de un modelo.
📓 Reto del cuaderno
Los residuos son la diferencia entre el valor real y el predicho. Crea dos gráficos de dispersión: 1) precios reales (y_train) contra predichos, con una línea de y_train contra sí mismo (si las predicciones fueran perfectas, todos los puntos estarían en ella); 2) residuos contra precios predichos. Luego calcula la media y la asimetría (skew) de los residuos y grafícalos con displot. ¿Son cercanas a 0?
📖 Solución y explicación (verificada)
from scipy.stats import skew
predicted_values = regr.predict(X_train)
residuals = y_train - predicted_values
plt.scatter(x=y_train, y=predicted_values, alpha=0.6)
plt.plot(y_train, y_train, color='cyan') # la línea de la predicción perfecta
plt.xlabel('Precio real (miles)'); plt.ylabel('Precio predicho (miles)')
plt.show()
plt.scatter(x=predicted_values, y=residuals, alpha=0.6)
plt.xlabel('Precio predicho (miles)'); plt.ylabel('Residuo')
plt.show()
round(residuals.mean(), 2) # 0.0
round(skew(residuals), 2) # ≈ 1.46¿Por qué mirar los residuos? Son los errores del modelo. Si tienen un patrón, el modelo tiene un sesgo sistemático. Lo ideal es que parezcan aleatorios, con una distribución cercana a la normal: media 0 y asimetría 0 (simétrica).
Resultado verificado: la media es 0 (siempre lo es en una regresión lineal con intercepto, sobre los datos de entrenamiento), pero la asimetría es 1,46: los residuos tienen una cola larga a la derecha. En el gráfico se ve por qué: las zonas más caras (sobre todo las de 50) quedan muy por debajo de su precio real. El modelo se equivoca más con las viviendas caras: hay que mejorarlo.
💡 El error típico del modelo en los datos de prueba es de unos 5 870 dólares (RMSE verificado: 5,87 miles), sobre un precio promedio de 22 530: cerca del 26 %.
7. Transformar los datos: el logaritmo del precio
(Contenido del cuaderno del curso; la lección 598 lo resume.)
📝 Contenido original del curso (lección 598)
Aprendiste a mejorar un modelo de regresión con una transformación logarítmica de los datos.
📓 Retos del cuaderno
Hay dos opciones: cambiar de modelo o transformar los datos para que se ajusten mejor a un modelo lineal. ¿Es PRICE un buen candidato para una transformación logarítmica? Grafica su distribución y calcula su asimetría; crea la serie np.log(data['PRICE']), grafícala y calcula su asimetría: ¿cuál está más cerca de 0? Luego, con el mismo random_state, ejecuta una segunda regresión con el logaritmo del precio. ¿Mejora el r²? ¿Los coeficientes mantienen el signo? ¿Estar junto al río es positivo? ¿Qué pasa con los precios cuando hay más estudiantes por docente? Compara los residuos de ambos modelos lado a lado (colores indigo y navy).
El cuaderno explica que el logaritmo no afecta igual a todos los precios: "comprime" más los precios grandes que los pequeños.
📖 Solución y explicación (verificada)
import numpy as np
data['PRICE'].skew() # 1.11
log_prices = np.log(data['PRICE'])
log_prices.skew() # -0.33 (más cerca de 0)
X_train, X_test, log_y_train, log_y_test = train_test_split(X, log_prices,
test_size=0.2, random_state=10)
log_regr = LinearRegression()
log_regr.fit(X_train, log_y_train)
log_regr.score(X_train, log_y_train) # 0.793 (antes: 0.750)¿Qué hace el logaritmo? Convierte multiplicaciones en sumas: de 10 a 20 miles (el doble) hay la misma distancia que de 25 a 50 (también el doble). Por eso "aprieta" la cola de los precios altos y la distribución queda más simétrica.
Cómo se leen ahora los coeficientes: en un modelo con log del precio, un coeficiente pequeño b significa aproximadamente un cambio del 100·b % en el precio. Verificado con np.exp(b) - 1:
| Variable | Coeficiente (log) | Efecto aproximado en el precio |
|---|---|---|
CHAS |
+0,080 | Junto al río: +8,4 % |
RM |
+0,073 | Una habitación más: +7,6 % |
PTRATIO |
−0,035 | Un estudiante más por docente: −3,4 % |
Los signos se mantienen: estar junto al río suma valor y tener más estudiantes por docente resta.
Residuos del modelo log (verificado): media 0 y asimetría 0,09 (antes 1,46): mucho más cerca de una distribución simétrica.
8. Probar el modelo y valorar una propiedad
(Contenido del cuaderno del curso; la lección 598 lo resume.)
📝 Contenido original del curso (lección 598)
Aprendiste a especificar tus propios valores para las características y usar el modelo para hacer una predicción.
📓 Retos del cuaderno
- Compara el r² de los dos modelos con los datos de prueba. ¿Cuál es mejor? ¿Es mayor o menor que en entrenamiento? ¿Por qué?
- Estima el precio de la propiedad promedio (el promedio de cada característica), en logaritmo y en dólares (revierte con
np.exp). - Manteniendo el promedio en las demás variables, valora una propiedad: junto al río, 8 habitaciones, 20 estudiantes por docente, distancia 5, contaminación alta (percentil 75 de
NOX) y pobreza baja (percentil 25 deLSTAT).
📖 Solución y explicación (verificada)
| Modelo | r² entrenamiento | r² prueba |
|---|---|---|
| Precio original | 0,750 | 0,671 |
| Logaritmo del precio | 0,793 | 0,745 |
El modelo log es mejor en ambos. El r² de prueba es menor que el de entrenamiento: es normal, porque el modelo se ajustó a los datos de entrenamiento y los de prueba son "nuevos". Si la diferencia fuera enorme, sospecharíamos sobreajuste.
features = data.drop(['PRICE'], axis=1)
property_stats = pd.DataFrame(data=[features.mean().values], columns=features.columns)
log_estimate = log_regr.predict(property_stats)[0] # 3.03
np.exp(log_estimate) * 1000 # ≈ 20 703 dólares
property_stats['CHAS'] = 1
property_stats['RM'] = 8
property_stats['PTRATIO'] = 20
property_stats['DIS'] = 5
property_stats['NOX'] = data.NOX.quantile(q=0.75)
property_stats['LSTAT'] = data.LSTAT.quantile(q=0.25)
np.exp(log_regr.predict(property_stats)[0]) * 1000 # ≈ 25 792 dólaresDos detalles importantes:
- Revertir el logaritmo: el modelo predice
log(precio);np.explo devuelve a miles de dólares y* 1000a dólares. - La vivienda "promedio" vale 20 703, no 22 530 (el precio promedio). Al revertir un logaritmo se obtiene un valor más parecido a la mediana (21 200) que a la media. No es un error: es una propiedad de la transformación.
Una estimación no es un número exacto: con un error típico de alrededor del 19 % en los datos de entrenamiento (desviación de los residuos log ≈ 0,19), la propiedad de 25 792 dólares podría valer, de forma razonable, entre unos 21 000 y 31 000 dólares. Un buen tasador siempre comunica un rango.
Resumen de la lección 598: .pairplot(); dividir en entrenamiento y prueba; regresión multivariable; evaluar por el signo de los coeficientes; patrones en los residuos; transformación logarítmica; predecir con tus propios valores. "¡El proyecto de ciencia de datos más desafiante hasta ahora!".
⚠️ Errores frecuentes
| Error | Síntoma | Solución |
|---|---|---|
Olvidar np.exp |
"La casa vale 3,03 dólares" | Revierte la transformación |
Olvidar * 1000 |
"La casa vale 20,7 dólares" | PRICE está en miles |
| Columnas en otro orden o con otros nombres al predecir | ValueError: The feature names should match those that were passed during fit |
Construye property_stats a partir de features.columns |
Usar log_y_test con el modelo original |
r² absurdo (negativo) | Cada modelo con su objetivo |
Predecir con valores imposibles (RM = 20) |
Un número "válido" pero sin sentido | Compara con el rango de los datos |
9. 📖 Ética de los datos: la columna B
(Sección creada para esta adaptación.)
El cuaderno describe B como 1000(Bk − 0,63)², donde Bk es la proporción de población negra de la zona. Es una variable construida con base en la raza, en una ciudad de los años 70 con fuerte segregación residencial.
¿Por qué importa?
- Un modelo que usa la raza de los vecinos para fijar precios puede reproducir la discriminación del pasado (en Estados Unidos, por ejemplo, la práctica de negar créditos en barrios de mayoría negra, conocida como redlining).
- Por esta razón, scikit-learn eliminó el conjunto de datos de Boston en la versión 1.2. Verificado:
from sklearn.datasets import load_bostonproduce unImportErrorcuyo mensaje explica que "el conjunto de datos de precios de vivienda de Boston tiene un problema ético".
¿Y si quitamos B? Verificado con la misma división de datos:
| Modelo log | r² entrenamiento | r² prueba |
|---|---|---|
Con B |
0,793 | 0,745 |
Sin B |
0,782 | 0,758 |
Sin B, el modelo es igual de bueno con los datos nuevos (incluso un poco mejor). Una variable problemática no siempre aporta lo que parece.
Preguntas para el debate en clase:
- ¿Es aceptable usar una variable si mejora un poco el modelo, aunque sea injusta?
LSTAT("estatus bajo") también describe a las personas de la zona. ¿Tiene el mismo problema? ¿Por qué sí o por qué no?- Si construyeras un tasador para tu municipio, ¿qué variables no usarías? ¿Cómo lo decidirías?
💡 Los datos también son de 1970: precios, contaminación y ciudad han cambiado. El modelo sirve para aprender regresión, no para valorar viviendas hoy.
10. 🚀 Proyecto del día: el tasador
(Integra los objetivos 597 y los puntos de aprendizaje 598.)
Problema
La empresa inmobiliaria quiere una herramienta que cualquier persona del equipo pueda usar sin saber programación.
Contexto
El modelo debe ser honesto: dar un rango, advertir cuando los datos pedidos están fuera de lo conocido y no usar variables injustas.
Objetivo
Una función tasar(caracteristicas) y un informe del modelo.
Requisitos funcionales
- Todo el análisis del cuaderno (secciones 1 a 8), con los textos en español.
- El modelo final con logaritmo del precio y sin la columna
B. tasar(dic)recibe un diccionario solo con las características que el usuario conoce (por ejemplo,{'RM': 7, 'CHAS': 1}), usa el promedio para las demás y devuelve la estimación en dólares y un rango.- La función advierte si algún valor está fuera del rango de los datos (mínimo y máximo de cada columna).
- Tres ejemplos de uso documentados.
Restricciones técnicas
- La función no debe modificar
datani el modelo. - Predicción con un DataFrame con las mismas columnas del entrenamiento.
- Docstring con parámetros, retorno y ejemplo.
Planificación
- Comprender la tarea. 2. Entradas: el CSV y un diccionario. 3. Procesos: explorar → dividir → modelar → evaluar → transformar → predecir. 4. Salidas: estimación, rango y advertencias. 5. Dividir en funciones:
preparar_fila,validar,tasar. 6. Diseñar la firma. 7. Implementar. 8. Probar. 9. Corregir. 10. Documentar.
Construcción paso a paso
- Versión 1: exploración y gráficos.
- Versión 2: división y primer modelo; coeficientes y residuos.
- Versión 3: modelo log, comparación y modelo sin
B. - Versión 4:
tasarcon el promedio ynp.exp. - Versión final: rango y advertencias.
Pruebas
| Prueba | Resultado esperado (verificado) |
|---|---|
| Formas de la división | 404 y 102 filas |
r² de prueba (original / log / log sin B) |
0,671 / 0,745 / 0,758 |
| Asimetría de los residuos (original / log) | 1,46 / 0,09 |
tasar({}) |
Cerca de 20 700 dólares (con B) |
tasar({'RM': 20}) |
Una estimación y una advertencia |
tasar({'RM': 7}) > tasar({'RM': 6}) |
Verdadero |
🧪 Prueba y depuración
X = data.drop('PRICE')
X_train, y_train, X_test, y_test = train_test_split(X, data.PRICE, test_size=0.2)
modelo = LinearRegression().fit(X_train, np.log(y_train))
casa = pd.DataFrame({'RM': [8], 'CHAS': [1]})
print(modelo.predict(casa) * 1000)Encuentra cinco problemas: drop('PRICE') sin axis=1 (KeyError); el orden al desempacar está mal (X_train, X_test, y_train, y_test); falta random_state (resultados distintos en cada ejecución); casa solo tiene 2 de las 13 columnas (ValueError de nombres de características); y falta np.exp para revertir el logaritmo.
⭐ Extensión del proyecto
- Reto 1: una pequeña interfaz con
input()(Días 1 a 10) o con Tkinter (Día 27) para usartasar. - Reto 2: prueba otras divisiones (
random_statede 0 a 20) y grafica cómo varía el r² de prueba. ¿Qué tan estable es el modelo? - Reto 3: investiga la validación cruzada (
cross_val_score) y explica por qué es mejor que una sola división.
11. 🌟 Proyecto extra: ¿cuánto vale un celular usado?
(Proyecto creado para esta adaptación; no existe en el curso original.)
Situación
En el colegio muchos estudiantes compran y venden celulares usados. ¿Qué precio es justo?
Requisitos
- Recopilen a mano (sin programas automáticos que violen las condiciones de uso de los sitios) al menos 80 anuncios de celulares usados de sitios de clasificados colombianos: precio, marca, año de lanzamiento, almacenamiento (GB), RAM, estado (1–5) y si incluye accesorios. No copien nombres ni teléfonos de los vendedores.
- Limpien los datos (precios escritos con puntos, "$", "negociable").
- Exploren con
pairplotyjointplot. - Dividan en entrenamiento y prueba y ajusten una regresión multivariable; revisen signos de coeficientes y residuos.
- Prueben el logaritmo del precio. ¿Mejora?
- Creen
tasar_celular(dic)con rango y advertencias.
Decisiones que debes tomar (y justificar)
- ¿Cómo conviertes la marca (texto) en números? (Investiga "variables dummy" y
pd.get_dummies.) - ¿Qué haces con anuncios de precios absurdos?
- ¿Con cuántos datos confiarías en tu modelo?
Casos de prueba
- Un celular más nuevo, con igual estado y almacenamiento, debe estimarse más caro.
- La función advierte si el año es posterior al actual.
12. 🏆 Proyecto integrador final: Laboratorio de Datos Abiertos
(Proyecto integrador creado para esta adaptación. Se construyó por etapas desde el Día 76 y reúne los conceptos de los Días 76 a 81. Complementa el Observatorio de Datos del Colegio de los Días 72 a 75.)
Situación problema
Las decisiones públicas (dónde invertir en educación, cómo reducir la contaminación, qué municipios necesitan más apoyo) se toman muchas veces sin mirar los datos, aunque Colombia publica miles de conjuntos de datos abiertos.
Contexto
Con plotly, NumPy, seaborn, SciPy y scikit-learn, un equipo de estudiantes puede ir de una pregunta a un modelo que haga estimaciones… siempre que trabaje con rigor y ética.
Pregunta orientadora
¿Qué factores se relacionan con un problema de nuestra región, qué tan bien podemos estimarlo con datos abiertos y cómo lo comunicamos de forma honesta?
Objetivo
Construir un cuaderno de análisis reproducible que responda la pregunta con gráficos interactivos, una prueba estadística y un modelo de regresión multivariable evaluado con datos de prueba, más un informe y una presentación para la comunidad.
Requerimientos
Funcionales
- Limpieza (Días 76 y 78): duplicados con
subsetjustificado, conversión de texto a número, datos basura documentados. - Visualización interactiva (Días 76 y 79): mínimo 5 gráficos de plotly de al menos 4 tipos (barras, cajas, dispersión, dona, mapa o sunburst).
- Cálculo con NumPy (Día 77): normalización con broadcasting,
np.where, correlaciones. - Regresión simple (Día 78):
regploty modelo con una variable, con interpretación. - Distribuciones (Días 79 y 80): histograma, KDE y comparación entre grupos con cajas y
lmplot. - Prueba estadística (Día 80): comparación de dos grupos con la prueba t de Welch, bien interpretada.
- Modelo multivariable (Día 81): división entrenamiento/prueba, coeficientes, residuos, transformación si hace falta, r² de prueba y una función de estimación con rango.
- Conclusiones que separen lo que los datos muestran de lo que no pueden mostrar.
No funcionales
- Reproducibilidad: Reiniciar y ejecutar todo funciona;
random_statefijo;requirements.txtcon las bibliotecas. - Compatibilidad: código que funcione con pandas 3, SciPy y scikit-learn actuales (sin
scipy.misc, sinload_boston). - Ética: ninguna variable que discrimine por raza, etnia, sexo u otra condición protegida sin una justificación explícita; ningún dato que identifique a personas.
- Accesibilidad: colores con contraste suficiente, textos en español, ejes con unidades.
Restricciones
- Fuentes abiertas y citadas (datos.gov.co, DANE, ICFES, IDEAM u otras oficiales).
- Nada de afirmaciones causales sin justificación.
- El informe para la comunidad cabe en una página y usa como máximo 2 gráficos.
Etapas de desarrollo
| Etapa | Día | Producto |
|---|---|---|
| 1. Tema, preguntas, limpieza y 3 gráficos de plotly | 76 | Secciones 1–2, FUENTES.md |
| 2. Variables preparadas con NumPy | 77 | Sección 3 |
| 3. Burbujas y regresión simple | 78 | Sección 4 |
| 4. Distribuciones, categorías y mapa o sunburst | 79 | Sección 5 |
| 5. Comparación de grupos y prueba t | 80 | Sección 6 |
| 6. Modelo multivariable, informe y presentación | 81 | Sección 7, informe, presentación |
Etapa 6 (hoy)
- Divide tus datos en entrenamiento y prueba con
random_statefijo. - Ajusta la regresión multivariable; revisa los signos de los coeficientes y explica los "raros".
- Analiza los residuos (gráficos, media y asimetría); prueba una transformación logarítmica si la variable objetivo es asimétrica.
- Compara modelos con el r² de prueba y revisa si alguna variable plantea problemas éticos (sección 9).
- Crea la función de estimación con rango y advertencias.
- Redacta el informe y prepara la presentación.
Entregables
- La carpeta del proyecto:
laboratorio.ipynb,data/,FUENTES.md,requirements.txtyREADME.md(cómo ejecutarlo). - Los gráficos interactivos en HTML.
- El informe de una página para la comunidad.
- Una presentación de 5 minutos: la pregunta, un gráfico, el modelo (qué tan bien predice) y una limitación.
- El cuaderno de errores del bloque (al menos 12 entradas).
Casos de prueba (lista de verificación)
| # | Comprobación |
|---|---|
| 1 | Reiniciar y ejecutar todo termina sin errores |
| 2 | Cada conjunto de datos tiene su fila en FUENTES.md |
| 3 | Los duplicados y conversiones están justificados y el número de filas se reporta antes y después |
| 4 | Cada gráfico tiene título, ejes con unidades y fuente |
| 5 | La prueba t usa equal_var=False y su interpretación no dice "probabilidad de que la hipótesis sea cierta" |
| 6 | El modelo se evalúa con datos de prueba y se reporta el r² de ambos conjuntos |
| 7 | Los residuos se analizan con gráfico, media y asimetría |
| 8 | Las predicciones incluyen un rango y advierten valores fuera de los datos |
| 9 | Ninguna conclusión afirma causalidad sin justificación |
| 10 | Ninguna variable discrimina sin justificación; ningún dato identifica a personas |
Criterios técnicos de funcionamiento
| Criterio | Nivel básico | Nivel alto |
|---|---|---|
| Limpieza | Elimina faltantes y duplicados | Justifica cada decisión y detecta errores contrastando columnas |
| Visualización | Gráficos correctos | Gráficos interactivos, honestos y accesibles, cada uno responde a una pregunta |
| Estadística | Calcula promedios y aplica la prueba t | Elige la prueba adecuada, interpreta bien el valor p y distingue significancia de importancia |
| Modelado | Ajusta una regresión y reporta el r² | Divide los datos, analiza residuos, transforma, compara modelos y comunica la incertidumbre |
| Ética | No usa datos personales | Evalúa y discute variables problemáticas con evidencia (con y sin la variable) |
| Comunicación | Informe con resultados | Informe claro para personas sin formación técnica, con limitaciones explícitas |
Posibles extensiones
- Publicar el informe como página web con Flask (Días 54 a 71) o como parte del portafolio (Día 83).
- Automatizar la descarga de datos con
requestsdesde la API de datos.gov.co (Día 33). - Probar otros modelos de scikit-learn (árboles de decisión) y compararlos con la regresión lineal usando los mismos datos de prueba.
13. 🧩 Comprueba lo aprendido
Conceptuales
- ¿Por qué se evalúa un modelo con datos de prueba y no con los de entrenamiento?
- ¿Qué significa "manteniendo lo demás constante" al interpretar un coeficiente?
- ¿Qué son los residuos y qué patrón no quieres ver en ellos?
- ¿Por qué el logaritmo reduce la asimetría de los precios?
- ¿Por qué scikit-learn retiró el conjunto de datos de Boston?
Análisis de código
pd.DataFrame(log_regr.coef_, index=X_train.columns, columns=['coef']).sort_values('coef')¿Qué muestra? ¿Qué variable aparece primero y por qué no significa que sea "la más importante"?
Predicción
Si el coeficiente de CHAS en el modelo log es 0,08, ¿cuánto más cara es, aproximadamente, una zona junto al río que una idéntica lejos de él?
Depuración
"Mi r² de prueba es 0,99". ¿Qué error probable cometiste?
Modificación
Cambia el tasador para que muestre la estimación en pesos colombianos, leyendo la tasa de cambio de una variable. ¿Tiene sentido convertir precios de 1970 con la tasa de hoy? ¿Por qué?
Creación
Escribe comparar_modelos(X, y, lista_de_columnas) que ajuste un modelo por cada conjunto de columnas y devuelva una tabla con el r² de entrenamiento y de prueba de cada uno.
14. 🤖 Programar con apoyo de IA
Ciclo de trabajo: formula el problema → inténtalo tú → consulta a la IA → lee y entiende → prueba → compara con la documentación oficial → modifica → explica con tus palabras.
Actividad 1 — Código obsoleto. Pide a una IA "cargar el conjunto de datos de Boston con scikit-learn". Si propone load_boston, ejecútalo y lee el mensaje de error completo. ¿Qué alternativas propone la documentación de scikit-learn?
Actividad 2 — Revisar una interpretación. Muestra a una IA la tabla de coeficientes del modelo original y pregúntale qué variable es "la más importante". Si responde NOX por tener el coeficiente más grande, explícale el problema de las unidades y compara con tu análisis.
Actividad 3 — Ética. Pregunta a una IA si es correcto usar la columna B para predecir precios. Compara su argumento con tu tabla con y sin B y con el mensaje de scikit-learn. Escribe tu propia posición en 5 líneas.
15. 🛠️ Correcciones técnicas al material original
| Archivo | Afirmación o comportamiento del curso | Observación | Tratamiento |
|---|---|---|---|
597 |
"Todos los retos están en el cuaderno" | El cuaderno no está en los archivos suministrados; se consultó una copia pública y sus retos se identifican como "📓 Reto del cuaderno" | Encabezado y secciones 1–8 |
| Cuaderno | Conjunto de datos de Boston con la columna B |
Variable basada en la raza; scikit-learn retiró el conjunto de datos en la versión 1.2 (verificado); sin B el r² de prueba no empeora (0,758, verificado) |
Sección 9 |
| Cuaderno | PRICE como "precio de la vivienda" |
Es el valor mediano de una zona, en miles, limitado a 50 (16 zonas, verificado) | Sección 1 |
| Cuaderno | x=['No', 'Yes'] para CHAS |
Correcto solo si value_counts devuelve 0 primero (verificado aquí) |
Sección 2 |
| Cuaderno | plt.figure(dpi=...) antes de sns.displot |
displot crea su propia figura |
Sección 2 |
| Cuaderno | Estimación de la propiedad "promedio" | Revertir el logaritmo da un valor cercano a la mediana (≈ 20 703 contra una media de 22 530, verificado) | Sección 8 |
597 |
"Evaluate how your model's coefficients and residuals" | Errata en el original (falta un verbo) | — |
16. 📋 Matriz de trazabilidad
| Contenido original | Archivo de origen | Concepto desarrollado | Ejercicio original | Reto adicional | Proyecto relacionado |
|---|---|---|---|---|---|
| Boston en los 70, tasador, características, 6 pasos del día, cuaderno y CSV | 597 |
Sección 0 | Preparar el cuaderno | — | Sección 10 |
.pairplot() para detectar relaciones |
598 |
Sección 3 | Retos del cuaderno (pairplot, jointplot) |
Mapa de calor; multicolinealidad | Secciones 10, 11 y 12 |
| División en entrenamiento y prueba | 598 |
Sección 4 | Reto del cuaderno | Estabilidad con varios random_state |
Secciones 10, 11 y 12 |
| Regresión multivariable y signo de los coeficientes | 598 |
Sección 5 | Retos del cuaderno | Unidades de los coeficientes | Secciones 10, 11 y 12 |
| Patrones en los residuos | 598 |
Sección 6 | Retos del cuaderno | RMSE | Secciones 10 y 12 |
| Transformación logarítmica | 598 |
Sección 7 | Retos del cuaderno | Interpretación porcentual | Secciones 10, 11 y 12 |
| Predicción con valores propios | 598 |
Sección 8 | Retos del cuaderno | Rango de la estimación | Secciones 10 y 11 |
Exploración, describe, CHAS, displot, barras de plotly |
Cuaderno (no suministrado) | Secciones 1 y 2 | Retos del cuaderno | Censura en 50 | Sección 10 |
| (No existe en el curso original) | — | Sección 9 | — | Ética de la columna B | Secciones 10 y 12 |
| (No existe en el curso original) | — | — | — | Celulares usados | Sección 11 |
| (No existe en el curso original) | — | — | — | Laboratorio de Datos Abiertos, versión final | Sección 12 |
17. 📦 Evidencias de aprendizaje del Día 81
- El cuaderno completo del tasador de Boston con la tabla de pruebas superada.
- La función
tasardocumentada, con rango y advertencias. - La respuesta escrita al debate ético sobre la columna
B. - El tasador de celulares usados (proyecto extra).
- El Laboratorio de Datos Abiertos completo con todos sus entregables.
- Cuaderno de errores: quitar
PRICEsinaxis=1, desempacartrain_test_spliten otro orden, olvidarrandom_state, comparar coeficientes con distintas unidades, olvidarnp.expo* 1000, columnas distintas al predecir, evaluar con los datos de entrenamiento, usar variables discriminatorias, predecir fuera del rango de los datos.