Saltar al contenido
Volver al inicio 100 Días de Código en Python — Día 81: Proyecto final — predecir el precio de una vivienda con regresión multivariable

100 Días de Código en Python — Día 81: Proyecto final — predecir el precio de una vivienda con regresión multivariable

Nivel: Grado décimo Área: Tecnología e Informática Modalidad: Tutorial práctico Fuente: Curso "100 Days of Code: The Complete Python Pro Bootcamp" (Angela Yu, App Brewery). Contenido del Día 81 (nivel avanzado), proyecto final del bloque de ciencia de datos.

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).zip y 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. Con train_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_boston ya no existe) por un problema ético con una de sus columnas (sección 9).


Tabla de contenidos

  1. Antes de empezar: tasador de viviendas en Boston, 1970
  2. Explorar los datos
  3. Visualizar las variables una por una
  4. Relaciones entre variables: pairplot y jointplot
  5. Datos de entrenamiento y de prueba
  6. Regresión multivariable y sus coeficientes
  7. Analizar los residuos
  8. Transformar los datos: el logaritmo del precio
  9. Probar el modelo y valorar una propiedad
  10. 📖 Ética de los datos: la columna B
  11. 🚀 Proyecto del día: el tasador
  12. 🌟 Proyecto extra: ¿cuánto vale un celular usado?
  13. 🏆 Proyecto integrador final: Laboratorio de Datos Abiertos
  14. 🧩 Comprueba lo aprendido
  15. 🤖 Programar con apoyo de IA
  16. 🛠️ Correcciones técnicas al material original
  17. 📋 Matriz de trazabilidad
  18. 📦 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:

  1. Analizar y explorar los datos de precios de vivienda de Boston.
  2. Dividir los datos en entrenamiento y prueba.
  3. Ejecutar una regresión multivariable.
  4. Evaluar los coeficientes y los residuos del modelo.
  5. Usar una transformación de los datos para mejorar el modelo.
  6. 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.log y np.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)

Python
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. PRICE es el valor mediano de las viviendas de esa zona y RM, el promedio de habitaciones. Todo el análisis habla de zonas, no de casas individuales.

🛠️ Nota técnica (verificado): 16 zonas tienen PRICE = 50,0 exactamente, 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)

Python
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)

Python
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)

Python
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 PRICE de 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:

Texto
PRECIO_estimado = θ0 + θ1·RM + θ2·NOX + θ3·DIS + θ4·CHAS + ... + θ13·LSTAT
  1. Ejecuta la regresión con los datos de entrenamiento. ¿Qué r² obtienes?
  2. Imprime los coeficientes en un DataFrame. ¿RM es positivo? ¿Qué signo tiene LSTAT? ¿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)

Python
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" que RM (3,1): NOX varía entre 0,38 y 0,87, mientras que RM varí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: PRICE está 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)

Python
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)

Python
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

  1. 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é?
  2. Estima el precio de la propiedad promedio (el promedio de cada característica), en logaritmo y en dólares (revierte con np.exp).
  3. 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 de LSTAT).

📖 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.

Python
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ólares

Dos detalles importantes:

  • Revertir el logaritmo: el modelo predice log(precio); np.exp lo devuelve a miles de dólares y * 1000 a 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_boston produce un ImportError cuyo 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:

  1. ¿Es aceptable usar una variable si mejora un poco el modelo, aunque sea injusta?
  2. LSTAT ("estatus bajo") también describe a las personas de la zona. ¿Tiene el mismo problema? ¿Por qué sí o por qué no?
  3. 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

  1. Todo el análisis del cuaderno (secciones 1 a 8), con los textos en español.
  2. El modelo final con logaritmo del precio y sin la columna B.
  3. 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.
  4. La función advierte si algún valor está fuera del rango de los datos (mínimo y máximo de cada columna).
  5. Tres ejemplos de uso documentados.

Restricciones técnicas

  • La función no debe modificar data ni el modelo.
  • Predicción con un DataFrame con las mismas columnas del entrenamiento.
  • Docstring con parámetros, retorno y ejemplo.

Planificación

  1. 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: tasar con el promedio y np.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

Python
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 usar tasar.
  • Reto 2: prueba otras divisiones (random_state de 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

  1. 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.
  2. Limpien los datos (precios escritos con puntos, "$", "negociable").
  3. Exploren con pairplot y jointplot.
  4. Dividan en entrenamiento y prueba y ajusten una regresión multivariable; revisen signos de coeficientes y residuos.
  5. Prueben el logaritmo del precio. ¿Mejora?
  6. 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

  1. Limpieza (Días 76 y 78): duplicados con subset justificado, conversión de texto a número, datos basura documentados.
  2. 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).
  3. Cálculo con NumPy (Día 77): normalización con broadcasting, np.where, correlaciones.
  4. Regresión simple (Día 78): regplot y modelo con una variable, con interpretación.
  5. Distribuciones (Días 79 y 80): histograma, KDE y comparación entre grupos con cajas y lmplot.
  6. Prueba estadística (Día 80): comparación de dos grupos con la prueba t de Welch, bien interpretada.
  7. 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.
  8. Conclusiones que separen lo que los datos muestran de lo que no pueden mostrar.

No funcionales

  • Reproducibilidad: Reiniciar y ejecutar todo funciona; random_state fijo; requirements.txt con las bibliotecas.
  • Compatibilidad: código que funcione con pandas 3, SciPy y scikit-learn actuales (sin scipy.misc, sin load_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)

  1. Divide tus datos en entrenamiento y prueba con random_state fijo.
  2. Ajusta la regresión multivariable; revisa los signos de los coeficientes y explica los "raros".
  3. Analiza los residuos (gráficos, media y asimetría); prueba una transformación logarítmica si la variable objetivo es asimétrica.
  4. Compara modelos con el r² de prueba y revisa si alguna variable plantea problemas éticos (sección 9).
  5. Crea la función de estimación con rango y advertencias.
  6. Redacta el informe y prepara la presentación.

Entregables

  1. La carpeta del proyecto: laboratorio.ipynb, data/, FUENTES.md, requirements.txt y README.md (cómo ejecutarlo).
  2. Los gráficos interactivos en HTML.
  3. El informe de una página para la comunidad.
  4. Una presentación de 5 minutos: la pregunta, un gráfico, el modelo (qué tan bien predice) y una limitación.
  5. 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 requests desde 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

  1. ¿Por qué se evalúa un modelo con datos de prueba y no con los de entrenamiento?
  2. ¿Qué significa "manteniendo lo demás constante" al interpretar un coeficiente?
  3. ¿Qué son los residuos y qué patrón no quieres ver en ellos?
  4. ¿Por qué el logaritmo reduce la asimetría de los precios?
  5. ¿Por qué scikit-learn retiró el conjunto de datos de Boston?

Análisis de código

Python
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 tasar documentada, 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 PRICE sin axis=1, desempacar train_test_split en otro orden, olvidar random_state, comparar coeficientes con distintas unidades, olvidar np.exp o * 1000, columnas distintas al predecir, evaluar con los datos de entrenamiento, usar variables discriminatorias, predecir fuera del rango de los datos.