Selección de la base y del problema

El caso estima el precio que pide un vendedor por un Mazda 2 usado en OLX. La base 1 reúne los anuncios, con el precio en pesos colombianos, y la pregunta es cuánto de las diferencias de precio se explica con características del carro que cualquier comprador ve en el anuncio. La respuesta es el precio anunciado.

Ese precio es el que pide el vendedor, no el que paga el comprador. Las conclusiones valen, por tanto, para precios de anuncio y no para precios de venta.

library(readxl)    # leer el archivo de Excel
library(ggplot2)   # construir los gráficos
library(plotly)    # volver interactivos los gráficos

datos = read_excel("base1_mazdacol.xlsx")
dim(datos)
## [1] 314  11
names(datos)
##  [1] "precio"                "kilometraje"           "transmision"          
##  [4] "modelo"                "color"                 "ciudad"               
##  [7] "departamento"          "web-scraper-order"     "web-scraper-start-url"
## [10] "link"                  "link-href"

La base trae 314 anuncios y 11 columnas. Siete describen el carro (precio, kilometraje, transmisión, modelo, color, ciudad y departamento). Las otras cuatro son rastro de la descarga automática, con el orden de descarga, los enlaces y el texto del anuncio, y no entran al análisis.

Punto 1. Exploración y regresión lineal simple

1.1 Descripción de las variables y revisión de la base

Se trabaja con la base completa, sin excluir registros, y con las características del carro expresadas en unidades legibles.

datos$precio_mill = datos$precio / 1000000      # precio en millones de pesos
datos$antiguedad  = 2022 - datos$modelo         # años de uso (anuncios descargados en septiembre de 2022)
datos$km_mil      = datos$kilometraje / 1000    # kilometraje en miles de km
datos$transmision = factor(datos$transmision, levels = c("Automática", "Mecánica"))

# Región: los tres mercados más grandes y el resto del país
datos$region = ifelse(datos$departamento %in% c("Bogotá", "Antioquia", "ValledelCauca"), datos$departamento, "Resto")
datos$region = factor(datos$region, levels = c("Bogotá", "Antioquia", "ValledelCauca", "Resto"),
                      labels = c("Bogotá", "Antioquia", "Valle del Cauca", "Resto del país"))

# Color: los tres más comunes y el resto agrupado
datos$color_g = ifelse(datos$color %in% c("Gris", "Blanco", "Rojo"), datos$color, "Otro")
datos$color_g = factor(datos$color_g, levels = c("Gris", "Blanco", "Rojo", "Otro"))

# Texto que aparece al pasar el cursor sobre cada anuncio en los gráficos
datos$anuncio = paste0("Modelo ", datos$modelo, ", ", datos$transmision, ", ", datos$ciudad)
Variable Tipo Qué mide
precio_mill Cuantitativa (respuesta) Precio anunciado, en millones de pesos
antiguedad Cuantitativa Años del carro: 2022 menos el año del modelo
km_mil Cuantitativa Kilómetros recorridos, en miles
transmision Categórica binaria Automática o mecánica
region Categórica Bogotá, Antioquia, Valle del Cauca o resto del país
color_g Categórica Gris, blanco, rojo u otro

La antigüedad sustituye al año del modelo porque se interpreta mejor. Se mide contra 2022, ya que la marca de tiempo de la columna de orden de descarga indica que los anuncios se bajaron el 12 de septiembre de ese año, de modo que un modelo 2018 tiene 4 años. La región y el color se agrupan porque la base trae 20 departamentos y 14 colores, varios con uno o dos anuncios, y con tan pocos casos no se puede estimar nada estable para cada uno.

Faltantes y duplicados

colSums(is.na(datos[, c("precio", "kilometraje", "transmision", "modelo", "color", "departamento")]))
##       precio  kilometraje  transmision       modelo        color departamento 
##            0            0            0            0            0            0
sum(duplicated(datos))                    # filas exactamente iguales
## [1] 0
caracteristicas = c("precio", "kilometraje", "modelo", "transmision", "color", "ciudad")
sum(duplicated(datos[, caracteristicas])) # anuncios con las mismas características del carro
## [1] 1

No hay datos faltantes ni filas idénticas. Solo 1 par de anuncios comparte todas las características del carro, aunque con títulos y fechas de publicación distintos. Podrían ser el mismo carro publicado dos veces; la base no lo confirma y se conservan ambos.

Valores atípicos

Se aplica la regla de la caja, que marca como atípico el valor que supera el tercer cuartil en más de 1,5 veces el rango intercuartílico.

ggplotly(
  ggplot(datos, aes(x = "", y = precio_mill)) +
    geom_boxplot(fill = "lightblue") +
    labs(title = "Figura 1. Boxplot del precio anunciado", x = "", y = "Precio (millones de pesos)") +
    theme_minimal()
)
ggplotly(
  ggplot(datos, aes(x = "", y = km_mil)) +
    geom_boxplot(fill = "moccasin") +
    labs(title = "Figura 2. Boxplot del kilometraje", x = "", y = "Kilometraje (miles de km)") +
    theme_minimal()
)
# Límite superior de la regla de la caja: Q3 + 1,5 x rango intercuartílico
lim_precio = quantile(datos$precio_mill, 0.75) + 1.5 * IQR(datos$precio_mill)
lim_km     = quantile(datos$km_mil, 0.75) + 1.5 * IQR(datos$km_mil)

datos[datos$precio_mill > lim_precio, c("precio_mill", "modelo", "kilometraje", "transmision")]
precio_mill modelo kilometraje transmision
119.9 2021 29000 Automática
159.9 2022 0 Automática
169.8 2019 36026 Automática
107.5 2021 17909 Automática
datos[datos$km_mil > lim_km, c("precio_mill", "modelo", "kilometraje")]
precio_mill modelo kilometraje
29.9 1995 280000

Hay 4 precios atípicos, todos de modelos 2019 a 2022 anunciados por encima de 105 millones, y 1 kilometraje atípico: un modelo 1995 con 280.000 km. La regla no marca otros valores que también llaman la atención, como un carro de 2022 a 6,8 millones y 9 carros de más de diez años con menos de 1.000 km. Pueden ser errores de digitación o carros casi sin uso, y la base no permite distinguirlos.

Se conservan todos los anuncios. Son lo que se publicó, y los datos no ofrecen un criterio para separar errores de casos reales; descartar los extremos sería una decisión sin respaldo. La consecuencia es que estos anuncios pueden inflar el error de los modelos, y eso se tiene presente al leer los resultados.

1.2 Análisis exploratorio

Variables cuantitativas

cuantitativas = c("precio_mill", "antiguedad", "km_mil")
summary(datos[, cuantitativas])
##   precio_mill       antiguedad         km_mil      
##  Min.   :  6.80   Min.   : 0.000   Min.   :  0.00  
##  1st Qu.: 35.00   1st Qu.: 4.000   1st Qu.: 39.12  
##  Median : 42.00   Median : 7.000   Median : 79.00  
##  Mean   : 50.47   Mean   : 7.131   Mean   : 75.00  
##  3rd Qu.: 63.00   3rd Qu.:11.000   3rd Qu.:107.38  
##  Max.   :169.80   Max.   :27.000   Max.   :280.00
sapply(datos[, cuantitativas], sd)   # desviación estándar
## precio_mill  antiguedad      km_mil 
##   20.541624    4.324552   46.232983

El anuncio típico es un Mazda 2 de 7 años y 79 mil kilómetros, publicado en 42,0 millones (medianas). El promedio, 50,5 millones, queda por encima porque unos pocos anuncios muy caros lo arrastran, y por eso la mediana es la medida más fiel de lo típico.

# Líneas verticales de la mediana y el promedio
lineas = data.frame(medida = c("Mediana", "Promedio"),
                    valor  = c(median(datos$precio_mill), mean(datos$precio_mill)))

ggplotly(
  ggplot(datos, aes(x = precio_mill)) +
    geom_histogram(binwidth = 5, boundary = 0, fill = "steelblue", color = "white") +
    geom_vline(data = lineas, aes(xintercept = valor, color = medida), linewidth = 1) +
    scale_color_manual(values = c("Mediana" = "red", "Promedio" = "darkgreen")) +
    labs(title = "Figura 3. ¿Cuánto piden por un Mazda 2?", x = "Precio anunciado (millones de pesos)",
         y = "Número de anuncios", color = "") +
    theme_minimal()
)

El 49 % de los anuncios cuesta entre 30 y 45 millones. A la derecha hay una cola larga: el 16 % supera los 70 millones y 4 pasan de 100.

Entre 45 y 50 millones hay solo 2 anuncios, así que el histograma muestra dos masas separadas. La Figura 6 muestra de dónde sale ese hueco: los carros de hasta 6 años tienen una mediana de 66 millones y los de 7 años o más, de 36.

ggplotly(
  ggplot(datos, aes(x = antiguedad)) +
    geom_histogram(binwidth = 1, fill = "lightseagreen", color = "white") +
    labs(title = "Figura 4. Antigüedad de los carros", x = "Antigüedad (años)", y = "Número de anuncios") +
    theme_minimal()
)
ggplotly(
  ggplot(datos, aes(x = km_mil)) +
    geom_histogram(binwidth = 10, boundary = 0, fill = "darkorange", color = "white") +
    labs(title = "Figura 5. Kilometraje de los carros", x = "Kilometraje (miles de km)", y = "Número de anuncios") +
    theme_minimal()
)

Casi todos los carros tienen entre 0 y 15 años, y solo 1 pasa de ahí (el modelo 1995). El kilometraje está más repartido: la mitad de los anuncios queda entre 39 mil y 107 mil km, y la barra pegada a cero reúne carros publicados con casi ningún recorrido.

Variables categóricas

lapply(datos[, c("transmision", "region", "color_g")], table)   # número de anuncios por categoría
## $transmision
## 
## Automática   Mecánica 
##        174        140 
## 
## $region
## 
##          Bogotá       Antioquia Valle del Cauca  Resto del país 
##              51              83              55             125 
## 
## $color_g
## 
##   Gris Blanco   Rojo   Otro 
##    105     76     44     89

La transmisión se reparte casi por mitades, con 174 automáticos y 140 mecánicos. Bogotá, Antioquia y el Valle del Cauca reúnen el 60 % de los anuncios, y el gris, con 105, es el color más frecuente.

1.3 Relación entre el precio y los predictores

Variables cuantitativas: dispersión y correlación

cor(datos[, cuantitativas])
##             precio_mill antiguedad     km_mil
## precio_mill   1.0000000 -0.8295419 -0.6543672
## antiguedad   -0.8295419  1.0000000  0.7436836
## km_mil       -0.6543672  0.7436836  1.0000000
ggplotly(
  ggplot(datos, aes(x = antiguedad, y = precio_mill, text = anuncio)) +
    geom_point(color = "steelblue", alpha = 0.7, size = 2) +
    labs(title = paste("Figura 6. Precio y antigüedad. Correlación:", fmt(cor(datos$precio_mill, datos$antiguedad), 2)),
         x = "Antigüedad (años)", y = "Precio (millones de pesos)") +
    theme_minimal()
)
ggplotly(
  ggplot(datos, aes(x = km_mil, y = precio_mill, text = anuncio)) +
    geom_point(color = "darkorange", alpha = 0.7, size = 2) +
    labs(title = paste("Figura 7. Precio y kilometraje. Correlación:", fmt(cor(datos$precio_mill, datos$km_mil), 2)),
         x = "Kilometraje (miles de km)", y = "Precio (millones de pesos)") +
    theme_minimal()
)

El precio baja a medida que el carro es más viejo y más recorrido. Con la antigüedad la correlación es de -0,83 y la nube forma una franja estrecha de pendiente negativa. Con el kilometraje es de -0,65 y los puntos se dispersan más. Los cuatro anuncios de más de 100 millones, todos modelos 2019 a 2022, quedan por encima de la franja, y el modelo 1995 queda aislado a la derecha en ambos gráficos.

La Figura 6 también explica el hueco del histograma de precios. A partir de los 7 años casi ningún anuncio pasa de 47,5 millones, y hasta los 6 casi ninguno baja de ahí: solo 5 de los 314 anuncios son excepciones.

La antigüedad y el kilometraje también están muy relacionados entre sí (0,74), como cabía esperar: un carro más viejo ha tenido más tiempo para recorrer kilómetros. Es probable que cada una repita en parte la información de la otra.

Variables categóricas: comparación entre grupos

ggplotly(
  ggplot(datos, aes(x = transmision, y = precio_mill)) +
    geom_boxplot(fill = "lightblue") +
    labs(title = "Figura 8. Precio según la transmisión", x = "", y = "Precio (millones de pesos)") +
    theme_minimal()
)
ggplotly(
  ggplot(datos, aes(x = region, y = precio_mill)) +
    geom_boxplot(fill = "lightyellow") +
    labs(title = "Figura 9. Precio según la región", x = "", y = "Precio (millones de pesos)") +
    theme_minimal()
)
ggplotly(
  ggplot(datos, aes(x = color_g, y = precio_mill)) +
    geom_boxplot(fill = "mistyrose") +
    labs(title = "Figura 10. Precio según el color", x = "", y = "Precio (millones de pesos)") +
    theme_minimal()
)
# Mediana del precio y de la antigüedad en cada grupo
por_trans  = aggregate(cbind(precio_mill, antiguedad) ~ transmision, data = datos, FUN = median)
por_region = aggregate(cbind(precio_mill, antiguedad) ~ region, data = datos, FUN = median)
por_color  = aggregate(cbind(precio_mill, antiguedad) ~ color_g, data = datos, FUN = median)
por_trans
transmision precio_mill antiguedad
Automática 56.95 6
Mecánica 38.50 9
por_region
region precio_mill antiguedad
Bogotá 40.0 8
Antioquia 42.0 8
Valle del Cauca 56.5 5
Resto del país 41.5 8
por_color
color_g precio_mill antiguedad
Gris 42.0 8
Blanco 50.0 6
Rojo 57.4 5
Otro 37.5 9

En las tres comparaciones los grupos que se anuncian más caros son también los más nuevos, como muestra la antigüedad mediana de cada tabla.

  • Transmisión. Los automáticos tienen una mediana de 56,95 millones y los mecánicos de 38,50, pero los primeros son 3 años más nuevos (6 contra 9). Una parte de la diferencia de precio viene de la edad.
  • Región. Bogotá, Antioquia y el resto del país están entre 40 y 42 millones. El Valle del Cauca llega a 56,5, con carros de 5 años de mediana frente a 8 en las otras regiones.
  • Color. Los rojos (57,4 millones) y los blancos (50,0) se anuncian por encima de los grises (42,0) y de los otros colores (37,5), y también son más nuevos, con 5 y 6 años de mediana.

La antigüedad aparece detrás de casi todas las diferencias entre grupos. Eso la convierte en la primera candidata a predictor y obliga a leer con cautela cualquier diferencia entre categorías, porque puede ser la edad con otro nombre.

1.4 Tres modelos de regresión simple

Se proponen tres modelos con un solo predictor cada uno, estimados por mínimos cuadrados con lm:

  1. Antigüedad, la variable con la correlación más fuerte con el precio (-0,83).
  2. Kilometraje, la otra medida de desgaste que consulta cualquier comprador.
  3. Transmisión, la variable binaria de la base, automática o mecánica.
simple_edad  = lm(precio_mill ~ antiguedad, data = datos)
simple_km    = lm(precio_mill ~ km_mil, data = datos)
simple_trans = lm(precio_mill ~ transmision, data = datos)

summary(simple_edad)
## 
## Call:
## lm(formula = precio_mill ~ antiguedad, data = datos)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -71.764  -4.601  -0.772   3.182 103.057 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  78.5643     1.2518   62.76   <2e-16 ***
## antiguedad   -3.9403     0.1502  -26.24   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 11.49 on 312 degrees of freedom
## Multiple R-squared:  0.6881, Adjusted R-squared:  0.6871 
## F-statistic: 688.4 on 1 and 312 DF,  p-value: < 2.2e-16
summary(simple_km)
## 
## Call:
## lm(formula = precio_mill ~ km_mil, data = datos)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -60.471  -6.915  -0.489   6.727 108.002 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 72.27177    1.67502   43.15   <2e-16 ***
## km_mil      -0.29074    0.01902  -15.29   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 15.56 on 312 degrees of freedom
## Multiple R-squared:  0.4282, Adjusted R-squared:  0.4264 
## F-statistic: 233.6 on 1 and 312 DF,  p-value: < 2.2e-16
summary(simple_trans)
## 
## Call:
## lm(formula = precio_mill ~ transmision, data = datos)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -36.737 -14.044  -3.787  13.263 113.756 
## 
## Coefficients:
##                     Estimate Std. Error t value Pr(>|t|)    
## (Intercept)           56.044      1.486  37.705  < 2e-16 ***
## transmisionMecánica  -12.506      2.226  -5.618 4.28e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 19.61 on 312 degrees of freedom
## Multiple R-squared:  0.09187,    Adjusted R-squared:  0.08896 
## F-statistic: 31.56 on 1 and 312 DF,  p-value: 4.276e-08
ggplotly(
  ggplot(datos, aes(x = antiguedad, y = precio_mill)) +
    geom_point(color = "grey60", alpha = 0.7, size = 2) +
    geom_line(aes(y = predict(simple_edad)), color = "red", linewidth = 1) +
    labs(title = "Figura 11. Modelo 1: precio según la antigüedad", x = "Antigüedad (años)",
         y = "Precio (millones de pesos)") +
    theme_minimal()
)
ggplotly(
  ggplot(datos, aes(x = km_mil, y = precio_mill)) +
    geom_point(color = "grey60", alpha = 0.7, size = 2) +
    geom_line(aes(y = predict(simple_km)), color = "red", linewidth = 1) +
    labs(title = "Figura 12. Modelo 2: precio según el kilometraje", x = "Kilometraje (miles de km)",
         y = "Precio (millones de pesos)") +
    theme_minimal()
)
# Predicción del modelo para cada tipo de transmisión
pred_trans = data.frame(transmision = c("Automática", "Mecánica"))
pred_trans$precio_mill = predict(simple_trans, pred_trans)

ggplotly(
  ggplot(datos, aes(x = transmision, y = precio_mill)) +
    geom_boxplot(fill = "lightblue") +
    geom_point(data = pred_trans, color = "red", shape = 18, size = 5) +
    labs(title = "Figura 13. Modelo 3: precio según la transmisión", x = "", y = "Precio (millones de pesos)") +
    theme_minimal()
)

En la Figura 13 los rombos rojos son las predicciones del modelo y coinciden con el promedio de cada grupo: con un predictor binario, la regresión reproduce las dos medias.

1.5 Interpretación de los modelos simples

modelos = list("1. Antigüedad" = simple_edad, "2. Kilometraje" = simple_km, "3. Transmisión" = simple_trans)

# De cada modelo se extraen el intercepto, la pendiente, el R² y el valor p de la pendiente
resumen = data.frame(
  Intercepto = sapply(modelos, function(m) coef(m)[[1]]),
  Pendiente  = sapply(modelos, function(m) coef(m)[[2]]),
  R2         = sapply(modelos, function(m) summary(m)$r.squared),
  Valor_p    = sapply(modelos, function(m) summary(m)$coefficients[2, 4]))

data.frame(round(resumen[, 1:3], 3), Valor_p = format.pval(resumen$Valor_p, eps = 0.001))
Intercepto Pendiente R2 Valor_p
1. Antigüedad 78.564 -3.940 0.688 < 0.001
2. Kilometraje 72.272 -0.291 0.428 < 0.001
3. Transmisión 56.044 -12.506 0.092 < 0.001

Modelo 1, antigüedad. Un carro de 0 años se anuncia en promedio en 78,6 millones, y cada año de antigüedad resta 3,94 millones. El R² es 0,688: la antigüedad explica el 68,8 % de la variación del precio entre anuncios.

Modelo 2, kilometraje. Un carro con 0 km se anuncia en promedio en 72,3 millones, y cada 10.000 km adicionales restan unos 2,9 millones. El R² es 0,428, bastante menor que el de la antigüedad.

Modelo 3, transmisión. El intercepto es el promedio de la categoría de referencia, la transmisión automática: 56,0 millones. La pendiente es la diferencia con los mecánicos, que se anuncian 12,5 millones por debajo en promedio. El R² es 0,092: la transmisión sola dice poco del precio.

Significancia estadística. En los tres modelos el valor p de la pendiente es menor que 0,001, de modo que se rechaza que la pendiente sea cero con 314 anuncios. Que una relación sea significativa no la hace útil: la transmisión supera ese umbral y explica menos del 10 % de la variación.

Asociación y causalidad. Los coeficientes describen asociaciones, no efectos. La transmisión lo ilustra. El modelo asigna 12,5 millones menos a un mecánico, pero cambiar la caja de un carro no tiene por qué mover su precio en esa cantidad, porque los mecánicos de la base son además 3 años más viejos y al menos una parte de la diferencia es edad. El kilometraje se confunde de la misma forma con los años. Una lectura causal exigiría comparar carros iguales salvo en la característica de interés, y unos anuncios no permiten hacerlo.

1.6 Validación cruzada y MAE

El R² mide el ajuste sobre los datos con que se estimó el modelo. Para juzgar la predicción sobre carros nuevos se usa validación cruzada con 5 partes: los 314 anuncios se asignan al azar a 5 grupos y, en cada vuelta, los tres modelos se reestiman con lm en 4 grupos y se evalúan en el quinto. Todos los modelos comparten las mismas particiones. La métrica es el MAE, el error absoluto medio en millones de pesos, y la referencia es predecir siempre la mediana del precio de entrenamiento. Un modelo que no mejora esa referencia no aporta.

set.seed(2026)
parte = sample(rep(1:5, length.out = nrow(datos)))   # a qué parte queda asignado cada anuncio
table(parte)
## parte
##  1  2  3  4  5 
## 63 63 63 63 62
# Aquí se guardan las predicciones de cada anuncio: una columna por modelo
pred = matrix(NA, nrow = nrow(datos), ncol = 4, dimnames = list(NULL, c("mediana", "edad", "km", "trans")))

for (k in 1:5) {
  entrena = datos[parte != k, ]   # 4 partes para estimar
  prueba  = datos[parte == k, ]   # 1 parte para evaluar
  pred[parte == k, "mediana"] = median(entrena$precio_mill)
  pred[parte == k, "edad"]    = predict(lm(precio_mill ~ antiguedad, data = entrena), prueba)
  pred[parte == k, "km"]      = predict(lm(precio_mill ~ km_mil, data = entrena), prueba)
  pred[parte == k, "trans"]   = predict(lm(precio_mill ~ transmision, data = entrena), prueba)
}

# MAE de cada columna: promedio del error en valor absoluto, en millones de pesos
mae = colMeans(abs(datos$precio_mill - pred))

comparacion = data.frame(MAE_millones = round(mae, 2), R2 = round(c(NA, resumen$R2), 3),
                         row.names = c("Mediana (referencia)", rownames(resumen)))
comparacion
MAE_millones R2
Mediana (referencia) 15.87 NA
1. Antigüedad 5.81 0.688
2. Kilometraje 10.06 0.428
3. Transmisión 15.47 0.092
grafico_mae = data.frame(Modelo = factor(rownames(comparacion), levels = rev(rownames(comparacion))),
                         MAE = comparacion$MAE_millones)

ggplotly(
  ggplot(grafico_mae, aes(x = Modelo, y = MAE)) +
    geom_col(fill = c("grey80", "steelblue", "grey60", "grey60")) +
    coord_flip() +
    labs(title = "Figura 14. Error promedio en anuncios que el modelo no vio", x = "",
         y = "MAE (millones de pesos)") +
    theme_minimal()
)

Predecir siempre la mediana da un MAE de 15,87 millones. El modelo de antigüedad lo reduce a 5,81, el 37 % del error de la referencia; el de kilometraje lo deja en 10,06 y el de transmisión en 15,47, casi sin mejora.

error_edad = abs(datos$precio_mill - pred[, "edad"])
mean(error_edad < 5)   # proporción de anuncios con error menor a 5 millones
## [1] 0.6273885
median(error_edad)     # la mitad de los errores es menor que esta cifra
## [1] 3.713019

Con el modelo de antigüedad, el 63 % de los anuncios tiene un error menor a 5 millones y la mitad, menor a 3,7. El MAE queda por encima de esa mediana porque unos pocos anuncios atípicos tienen errores muy grandes.

Se elige el modelo de antigüedad por tres razones.

  • Precisión. Tiene el menor MAE, 5,81 millones frente a 15,87 de la referencia.
  • Coherencia. El signo negativo coincide con lo esperado: un carro pierde valor con los años.
  • Interpretabilidad. Se resume en una cifra, unos 4 millones menos por año.

El modelo tiene un límite. La recta se ajustó con carros de 0 a 15 años, que son casi toda la base. Extrapolada al modelo 1995, de 27 años, predice -27,8 millones (Figura 11), un precio imposible. Debe usarse dentro del rango observado, y su forma recta es una simplificación que el Punto 2 retoma con más variables.