Predecicción de los precios de vehículos usados
Introducción
\(\text{El problema}\)
Los precios de los vehículos nuevos en la industria los fija el fabricante con algunos costos adicionales incurridos por el Gobierno en forma de impuestos. Por lo tanto, los clientes que compran un automóvil nuevo pueden estar seguros de que el dinero que invierten es el ‘justo’. Pero que sucede con las personas que no se pueden permitir un auto de concesionario, y tienen que recurrir a la compra de un auto usado. Existe la necesidad de un sistema de predicción de precios de automóviles usados para determinar de manera efectiva el valor del automóvil en base a las características más importantes a la hora de evaluar un automóvil. Es importante conocer su valor de mercado real tanto al comprar como al vender.
Los datos utilizados en este proyecto se descargaron de Kaggle. Fue subido a [Kaggle] (https://www.kaggle.com/austinreese/craigslist-carstrucks-data), por Austin Reese usuario de Kaggle.com. Austin Reese extrajo estos datos de craigslist con un propósito sin fines de lucro. Contiene la mayor parte de la información relevante que Craigslist proporciona sobre las ventas de automóviles, incluidas variables como odometer, price, year, manufacturer, y otras 22 categorías.
Lectura y tratamiento de los datos
Librerías que serán usadas en éste trabajo.
library(dplyr)
library(tidyverse)
library(data.table)
library(knitr)
library(kableExtra)
library(DataExplorer)
library(FNN)
library(PCAmixdata)
library(caret)Procedemos a la lectura de los datos:
datos_DT <- setDT(read.csv(file = "vehicles.csv", header = TRUE))Duplicados
Verifiquemos si hay presencia de datos duplicados sobre la columna Id:
length(datos_DT$id[duplicated(datos_DT$id)])## [1] 0
Datos faltantes:
Procedemos a verificar la presencia de valores faltantes.
plot_missing(datos_DT, title = "Porcentaje de valores faltantes")Como podemos ver la variable county está completamente vacía, por tanto será eliminada, junto con otras variable innecesarias para el futuro modelo como url, region_url, image_url, lat, long, description, posting_date, VIN, region, paint_color, state.
datos_DT <- datos_DT[,-c(2, 3, 4, 15, 19, 20, 21, 22, 23, 24, 25, 26)]Pudimos observar que las primeras 27 observaciones están casi completamente vacías por tanto procedemos a eliminarlas:
kable(head(datos_DT, 27), align = 'c' )| id | price | year | manufacturer | model | condition | cylinders | fuel | odometer | title_status | transmission | drive | size | type |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 7222695916 | 6000 | NA | NA | ||||||||||
| 7218891961 | 11900 | NA | NA | ||||||||||
| 7221797935 | 21000 | NA | NA | ||||||||||
| 7222270760 | 1500 | NA | NA | ||||||||||
| 7210384030 | 4900 | NA | NA | ||||||||||
| 7222379453 | 1600 | NA | NA | ||||||||||
| 7221952215 | 1000 | NA | NA | ||||||||||
| 7220195662 | 15995 | NA | NA | ||||||||||
| 7209064557 | 5000 | NA | NA | ||||||||||
| 7219485069 | 3000 | NA | NA | ||||||||||
| 7218893038 | 0 | NA | NA | ||||||||||
| 7218325704 | 0 | NA | NA | ||||||||||
| 7217788283 | 0 | NA | NA | ||||||||||
| 7217147606 | 0 | NA | NA | ||||||||||
| 7209027818 | 0 | NA | NA | ||||||||||
| 7223509794 | 13995 | NA | NA | ||||||||||
| 7222753076 | 24999 | NA | NA | ||||||||||
| 7222206015 | 21850 | NA | NA | ||||||||||
| 7220030122 | 26850 | NA | NA | ||||||||||
| 7218423006 | 11999 | NA | NA | ||||||||||
| 7216672204 | 24999 | NA | NA | ||||||||||
| 7215617048 | 21850 | NA | NA | ||||||||||
| 7213839225 | 26850 | NA | NA | ||||||||||
| 7208549803 | 11999 | NA | NA | ||||||||||
| 7213843538 | 24999 | NA | NA | ||||||||||
| 7212631321 | 21850 | NA | NA | ||||||||||
| 7219973522 | 500 | NA | NA |
datos_DT <- datos_DT[-c(1:27),]Además tenemos otro problema es la existencia de valores vacíos en determinadas variables, por tanto primero realizaremos un reemplazo de éstas por valores NA, para determinar el porcentaje de valores faltantes.
datos_DT[,id := ifelse(id == '', NA, id)]
datos_DT[,price := ifelse(price == '', NA, price)]
datos_DT[,year := ifelse(year == '', NA, year)]
datos_DT[,manufacturer := ifelse(manufacturer == '', NA, manufacturer)]
datos_DT[,model := ifelse(model == '', NA, model)]
datos_DT[,condition := ifelse(condition == '', NA, condition)]
datos_DT[,cylinders := ifelse(cylinders == '', NA, cylinders)]
datos_DT[,fuel := ifelse(fuel == '', NA, fuel)]
datos_DT[,odometer := ifelse(odometer == '', NA, odometer)]
datos_DT[,title_status := ifelse(title_status == '', NA, title_status)]
datos_DT[,transmission := ifelse(transmission == '', NA, transmission)]
datos_DT[,drive := ifelse(drive == '', NA, drive)]
datos_DT[,size := ifelse(size == '', NA, size)]
datos_DT[,type := ifelse(type == '', NA, type)]Volvemos a revisar el verdadero porcentaje de valores faltantes:
plot_missing(datos_DT, title = "Porcentaje datos faltantes")Notemos que la variable size posee un alto porcentaje de datos NA, por tanto ésta debe de ser eliminada del dataset.
datos_DT <- datos_DT[,-13]Para la variable cylinders hay un \(41.62\%\) de datos faltantes, podemos imputar por la media por type, la justificación de esta decisión estaría dada por el siguiente test de asociación que hay entre las variables cylinder y type.
Dado que las variables son categóricas, una manera de verificar independencia o asociasión entre estas variables, es mediante el test chi-cuadrado. El test chi cuadrado de Pearson trabaja con frecuencia de eventos, es decir, con tablas de contingencia en las que se sumariza el número de eventos de cada tipo.
tabla <- table(as.character(datos_DT$cylinders), as.character(datos_DT$type), dnn = c("cilindraje", "Tipo"))
kable(tabla, caption = "Tabla de contingencia del número de obs. de cilindraje por tipo") | bus | convertible | coupe | hatchback | mini-van | offroad | other | pickup | sedan | SUV | truck | van | wagon | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10 cylinders | 66 | 24 | 61 | 1 | 0 | 1 | 43 | 98 | 172 | 116 | 534 | 55 | 38 |
| 12 cylinders | 0 | 29 | 48 | 0 | 0 | 0 | 7 | 3 | 77 | 12 | 10 | 0 | 0 |
| 3 cylinders | 12 | 18 | 74 | 113 | 6 | 0 | 44 | 5 | 143 | 110 | 37 | 7 | 29 |
| 4 cylinders | 48 | 1373 | 3215 | 5857 | 288 | 98 | 1314 | 775 | 32362 | 18267 | 1257 | 1001 | 3687 |
| 5 cylinders | 0 | 60 | 75 | 101 | 6 | 4 | 20 | 97 | 695 | 159 | 98 | 31 | 165 |
| 6 cylinders | 114 | 1852 | 3973 | 469 | 3388 | 341 | 5383 | 9868 | 18363 | 27731 | 7889 | 3953 | 1387 |
| 8 cylinders | 132 | 2043 | 5807 | 148 | 36 | 133 | 2367 | 16129 | 5115 | 9401 | 19256 | 1893 | 250 |
| other | 22 | 43 | 78 | 107 | 1 | 4 | 219 | 52 | 187 | 155 | 150 | 13 | 13 |
En nuestro caso el test que se realiza es el siguiente:
\(H_{0}\): Las variables son independientes, por lo que una variable no varía entre los distintos niveles de la otra variable.
\(H_{a}\): Las variables son dependientes, una variable varía entre los distintos niveles de la otra variable.
chisq.test(x = tabla)##
## Pearson's Chi-squared test
##
## data: tabla
## X-squared = 92327, df = 84, p-value < 2.2e-16
Dado que el p-valor es menor al 5% se rechaza la hipótesis nula, y se puede decir que las variables en cuestión se encuentran asociadas, por tanto; proseguimos con la imputación de los datos con la media de cilindros por tipo de auto. Para el caso en el que en ciertas observaciones no tengamos valores para cylinder ni para type, imputaremos con la moda de la variable cylinder:
#verificamos la moda en la variable cylinder
datos_DT %>% count(cylinders)## cylinders n
## 1: 10 cylinders 1455
## 2: 12 cylinders 209
## 3: 3 cylinders 655
## 4: 4 cylinders 77642
## 5: 5 cylinders 1712
## 6: 6 cylinders 94169
## 7: 8 cylinders 72062
## 8: other 1298
## 9: <NA> 177651
de la anterior tabla podemos ver que la moda de la variable cylinders es “6 cylinders”. Y procedemos con la imputación:
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "bus", names(which.max(tabla[,1])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "convertible", names(which.max(tabla[,2])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "coupe", names(which.max(tabla[,3])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "hatchback", names(which.max(tabla[,4])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "mini-van", names(which.max(tabla[,5])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "offroad", names(which.max(tabla[,6])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "other", names(which.max(tabla[,7])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "pickup", names(which.max(tabla[,8])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "sedan", names(which.max(tabla[,9])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "SUV", names(which.max(tabla[,10])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "truck", names(which.max(tabla[,11])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "van", names(which.max(tabla[,12])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & type == "wagon", names(which.max(tabla[,13])), cylinders)]
datos_DT[, cylinders := ifelse(is.na(cylinders) & is.na(type), "6 cylinders", cylinders)]Para las variable condition, type y drive, se procedará de la siguiente forma:
1.- observar el siguiente cuadro:
library(mice)
md.pattern(datos_DT[, c(6, 13, 12)], rotate.names = TRUE)## type drive condition
## 173385 1 1 1 0
## 90902 1 1 0 1
## 46616 1 0 1 1
## 23119 1 0 0 2
## 18471 0 1 1 1
## 13555 0 1 0 2
## 14304 0 0 1 2
## 46501 0 0 0 3
## 92831 130540 174077 397448
Notemos que existen 46501 observaciones para las cuales hay valores NA en estas 3 variables a la vez. Por lo cual iniciaremos eliminando esta observaciones.
nas <- which(is.na(datos_DT$condition) & is.na(datos_DT$drive) & is.na(datos_DT$type))
datos_DT <- datos_DT[-nas,]
rm(nas)Para la variable condition imputaremos en base a la categoría “año”, dado que la variable año no posee muchos valores NA. El criterio es el siguiente:
anios <- min(datos_DT$year, na.rm = TRUE):max(datos_DT$year, na.rm = TRUE) #rango de años de los vehiculos,
datos_DT[, condition := ifelse(is.na(condition) & year >= as.numeric(quantile(anios, probs = c(0))) & year < as.numeric(quantile(anios, probs = c(1/6))), "salvage", condition)]
datos_DT[, condition := ifelse(is.na(condition) & year >= as.numeric(quantile(anios, probs = c(1/6))) & year < as.numeric(quantile(anios, probs = c(2/6))), "fair", condition)]
datos_DT[, condition := ifelse(is.na(condition) & year >= as.numeric(quantile(anios, probs = c(2/6))) & year < as.numeric(quantile(anios, probs = c(3/6))), "good", condition)]
datos_DT[, condition := ifelse(is.na(condition) & year >= as.numeric(quantile(anios, probs = c(3/6))) & year < as.numeric(quantile(anios, probs = c(4/6))), "like new", condition)]
datos_DT[, condition := ifelse(is.na(condition) & year >= as.numeric(quantile(anios, probs = c(4/6))) & year < as.numeric(quantile(anios, probs = c(5/6))), "new", condition)]
datos_DT[, condition := ifelse(is.na(condition) & year >= as.numeric(quantile(anios, probs = c(5/6))) & year < as.numeric(quantile(anios, probs = c(1))), "excellent", condition)]
datos_DT[, condition := ifelse(is.na(condition) & is.na(year), "good", condition)]
rm(anios)Para el resto de incidencias de NA imputaremos por la media o por la moda de ser variable cualitativa:
md.pattern(datos_DT[, c(12, 13)], rotate.names = TRUE)## type drive
## 264287 1 1 0
## 69735 1 0 1
## 32026 0 1 1
## 14304 0 0 2
## 46330 84039 130369
Eliminamos estas 14304 observaciones donde type y drive tienes NAs:
datos_DT <- datos_DT[-which(is.na(datos_DT$drive) & is.na(datos_DT$type)),]table(datos_DT$drive)##
## 4wd fwd rwd
## 131904 105517 58892
Imputamos con el valor “4wd” para la variable drive,
datos_DT[, drive := ifelse(is.na(drive), "4wd", drive)]table(datos_DT$type)##
## bus convertible coupe hatchback mini-van offroad
## 517 7731 19204 16598 4825 609
## other pickup sedan SUV truck van
## 22110 43510 87056 77284 35279 8548
## wagon
## 10751
op <- par(mar = c(5,5,2,1) )
barplot(sort(table(datos_DT$type)), col = rainbow(13), las = 1, horiz = TRUE, main = "Número de vehículos por tipo")par(op) ## reset
rm(op)Dado que la variable type está bien distribuida, y solo hay 32026 obs. con NA, las eliminamos:
datos_DT <- datos_DT[-which(is.na(datos_DT$type)),]Para la variable manufacturer reeamplazamos por “Ford” que es la moda:
datos_DT[, manufacturer := ifelse(is.na(manufacturer), "ford", manufacturer)]Para la variable titlestatus reemplazamos por la moda “clean”:
datos_DT[, title_status := ifelse(is.na(title_status), "clean", title_status)]Para la variable odometer imputamos con la media:
media <- mean(datos_DT$odometer[!is.na(datos_DT$odometer)])
datos_DT[, odometer := ifelse(is.na(odometer), media, odometer)]
rm(media)Para la variable model es mejor no considerar esta variable dado que tiene muchos niveles:
datos_DT <- datos_DT[,-5]Para la variable fuel imputamos con la moda:
library(modeest)
datos_DT[, fuel := ifelse(is.na(fuel), mfv(fuel), fuel)]Para la variable transmission imputamos con la moda:
datos_DT[, transmission := ifelse(is.na(transmission), mfv(transmission), transmission)]Para la variable year imputamos por la moda:
moda_year <- mfv(datos_DT$year[!is.na(datos_DT$year)])
datos_DT[, year := ifelse(is.na(year), moda_year, year)]
rm(moda_year)Nos quedaron 17 observaciones con NA en la variable condition y las imputaremos por la moda:
moda_condition <- mfv(datos_DT$condition[!is.na(datos_DT$condition)])
datos_DT[, condition := ifelse(is.na(condition), moda_condition, condition)]
rm(moda_condition)
plot_missing(datos_DT, title = "Porcentaje de valores perdidos")Datos Atípicos
Notemos que en la variable price hay problemas de datos atípicos:
#minimo:
cat("El precio mínimo en el dataset es:", min(datos_DT$price))## El precio mínimo en el dataset es: 0
#maximo
cat("El precio máximo en el dataset es:",max(datos_DT$price))## El precio máximo en el dataset es: 3736928711
library(ggplot2)
ggplot(datos_DT, ) + aes(x=price, fill=condition) + geom_boxplot() + facet_wrap(~transmission) +
labs(title = "Gráfico de cajas por transmisión y por condición")Lo que se ha decidido hacer con la variable price es eliminar las observaciones que están por encima de \(\$100k\) y aquellos que están por debajo de \(\$750\)
cat("numero de obs. de precio mayor a 100k: ",length(which(datos_DT$price > 100000)))## numero de obs. de precio mayor a 100k: 482
cat("numero de obs. de precio menor a 750: ",length(which(datos_DT$price < 750)))## numero de obs. de precio menor a 750: 33811
datos_DT <- datos_DT[-which(datos_DT$price > 100000),]
datos_DT <- datos_DT[-which(datos_DT$price < 750),]Y para una mejor manipulación de la data vamos a separar las variables entre cuantitativas y cualitativas:
data.cut <- splitmix(datos_DT)
data.quanti<-as.data.frame(data.cut$X.quanti[-1])
data.quali<-as.data.frame(data.cut$X.quali)vemos si hay mas datos atípicos en las variables cuantitativas mediante el algoritmo de knn:
data.out <- get.knn(data.quanti,k=5)
kable(head(data.out$nn.dist,10))| 0.000000 | 0.00000 | 0.00000 | 0.00000 | 0.00000 |
| 0.000000 | 0.00000 | 0.00000 | 0.00000 | 0.00000 |
| 0.000000 | 0.00000 | 0.00000 | 0.00000 | 0.00000 |
| 0.000000 | 0.00000 | 0.00000 | 0.00000 | 0.00000 |
| 5.099019 | 9.00000 | 14.79865 | 14.79865 | 14.79865 |
| 0.000000 | 0.00000 | 0.00000 | 0.00000 | 0.00000 |
| 0.000000 | 0.00000 | 0.00000 | 0.00000 | 0.00000 |
| 3.000000 | 92.02717 | 100.00500 | 100.17984 | 179.08099 |
| 0.000000 | 0.00000 | 0.00000 | 0.00000 | 0.00000 |
| 0.000000 | 0.00000 | 0.00000 | 0.00000 | 0.00000 |
score <- rowMeans(data.out$nn.dist)
cota <- quantile(score,c(0.975))
index <- which(score>cota)
Out <- score[index]
cat("El total de datos atípicos en el dataset es:", length(Out))## El total de datos atípicos en el dataset es: 7494
valores <- as.factor(ifelse(score>cota,"Eliminar","No eliminar"))
obs <- c(1:length(score))
pd <- data.frame(score,valores,obs)
ggplot(pd, aes(x=obs,y=score, col=valores)) + geom_point() + labs(title = "Gráfico de las anomalías") +
xlab("Observaciones")rm(data.cut, data.out, cota, score, Out, pd, obs, valores)Procedemos a eliminar las observaciones correspondientes a estos datos atípicos:
datos_DT <- datos_DT[-index,]
rm(index)Estos valores se eliminaron del conjunto de datos porque estos precios son un ruido para los datos.
En segundo lugar, se descartaron los automóviles que tienen un valor de odómetro de más de 300.000 millas y menos de 10 millas. Y, por último, se descartaron los vehículos anteriores a 1985. Para nuestro análisis, estas observaciones pueden considerarse valores atípicos.
#Como la variable año está como tipo factor la cambiamos a tipo *Date*, para una mejor manipulación de los datos:
datos_DT[,3] <- format(as.Date(as.character(datos_DT$year), format = "%Y"), format = "%Y")
cat("numero de obs. cuyo odometer es mayor a 300k millas es:",length(which(datos_DT$odometer > 300000)))## numero de obs. cuyo odometer es mayor a 300k millas es: 244
cat("numero de obs. cuyo odometer es menor a 10 millas es:",length(which(datos_DT$odometer < 10)))## numero de obs. cuyo odometer es menor a 10 millas es: 1902
cat("el numero de vehículos anteriores al año 1985 son: ", length(which(datos_DT$year < "1985")))## el numero de vehículos anteriores al año 1985 son: 4967
datos_DT <- datos_DT[-which(datos_DT$odometer > 300000),]
datos_DT <- datos_DT[-which(datos_DT$odometer < 10),]
datos_DT <- datos_DT[-which(datos_DT$year < "1985"),]
datos_DT <- as.data.table(datos_DT)
rm(data.quali, data.quanti)Análisis de datos exploratorios (EDA)
Gráfico de barras de la condición de los vehículos.
ggplot(datos_DT, aes(x = condition, fill = condition)) + geom_bar() + geom_text(aes(label = scales::percent(((..count..)/sum(..count..))), y= ((..count..)/sum(..count..))), stat= "count", vjust = -.5) +
labs(title = "Gráfico de barras de la condición de los vehículos") + ylab("Número de vehículos") Podemos observar que la mayoría de vehículos se encuentra en una excelente condición con un 55.63%, seguido de vehículos en buena condición con un 36.96%.
Gráfico de barras de la condición de los vehículos por tipo,
ggplot(datos_DT) + geom_bar( aes(x = condition, fill = condition)) + facet_wrap(~type, scales = 'free') +
theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank()) +#esto es para quitar el etiquetado en el eje x porque queda feo
labs(title = "Condición de los vehículos por tipo", ylab = "Numero de vehículos") Notemos que inclusive por tipo de vehículos, se sigue la misma tendencia en la condición de los mismos.
Graficamos el numero de vehículos por condición dentro de un rango de años especificado (2000:2020), y por tipo de conducción ‘drive’:
datos_DT %>% filter(year %in% c(2000:2020)) %>%
ggplot() +
aes(x = condition, fill = condition, y = ((..count..)/1)) +
geom_bar() +
facet_wrap(~drive) +
geom_text(aes( label = paste0(round(((..count..)/sum(..count..))*100,1),"%"), y = ((..count..)/sum(..count..))), stat= "count", vjust = 1.5, size = 2.5) +
theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank()) +#esto es para quitar el etiquetado en el eje x porque queda feo
labs(x = "Condition", y = "Numero de vehículos", fill = "Condition", title = "Condición de los vehículos por tipo de conducción") al evaluar un automóvil, es importante comprender qué afecta su condición. La figura anterior muestra que los coches con tracción 4x4 son más duraderos y fiables. Se puede ver que los autos 4x4 son los más populares en términos de números. A largo plazo, pueden mantener su capacidad de funcionar mejor en comparación con el tren de transmisión rwd y fwd . 4wd tiene los números más altos de autos en ‘excelente’, ‘como nuevo’ y ‘buen’ estado. Por otro lado, debemos tener en cuenta que, en comparación con el número total, es difícil decir que los autos 4x4 tienen una tasa más alta de autos “excelentes” y “como nuevos”.
Histograma de los precios de los vehículos con detalle de la condición:
ggplot(data = datos_DT) + aes(x = price, fill = condition) + geom_histogram(bins = 50, color = 'black') +
theme_minimal() + labs(title = "Histograma del precio de vehículos detalle de la condición", y = "Número de observaciones") Notemos que los precios están acumulados entre los 5000 y 20000 dólares.
Densidad de los precios de los vehículos por condición:
ggplot(data = datos_DT) +
aes(x = price, fill = condition) +
geom_density(position = 'stack') +
facet_grid(condition~., scales = 'free') +
theme(legend.position="none") +
labs(title = "Densidad de la variable precio por condición")Se puede observar que para las condiciones ‘fair’ y ‘salvage’ los precios son muy bajos y con poca variabilidad, lo cual hace mucho sentido. Por otro lado para la condición ‘good’ se tiene una gran variabilidad en los precios.
En el gráfico de violín se representa, en el eje vertical, la distribución de los datos como si se tratara de un gráfico de densidad, y se traza la imagen especular en el eje a fin de obtener el resultado en forma de ‘violín’ como se observa en la gráfica. Si sobre este gráfico superponemos el boxplot:
ggplot(data = datos_DT) + aes(x = condition, y = price) +
geom_jitter(size = 1, color = 'gray', alpha = 0.5) +
geom_violin(aes(fill = condition), color = 'black', alpha = 0.8) +
geom_boxplot(color = 'black', alpha = 0.7) +
labs(title = "Gráfico de violín por condición") Podemos entender mejor la relación entre la distribución de los datos y el gráfico de cajas asociados a estos.
Gráfico de dispersión entre las variables odometro y precio:
ggplot(datos_DT, aes(x=odometer, y=price)) +
geom_point() +
geom_density2d() +
labs(title = "Gráfico de dispersión entre odometer y price") Podemos notar que a medida que el aumenta el odometro (kilometraje), el precio disminuye.
Gráfico de distribución de los fabricantes, solo de los principales fabricantes:
tabla1 <- sort(table(datos_DT$manufacturer), decreasing = TRUE)
DF <- data.frame(tabla1)
ggplot(DF[1:20,], ) + aes(x = Var1, y = Freq) +
geom_bar(stat ="identity", width = 0.5, fill = rainbow(20), colour = "black") +
theme(axis.text.x = element_text(angle = 60, margin = margin(b = -0.5, unit = "cm"), vjust = 0.7)) +
labs(title = "Distribución de vehículos por fabricantes", x = "Fabricante", y = "Frecuencia")El fabricante de un automóvil es otra variable importante en el mercado de automóviles usados. Ford y Chevrolet son los fabricantes dominantes en Norteamérica. Toyota, Honda y Nissan siguen el orden como grandes fabricantes. Se puede concluir que los automóviles japoneses tienen una participación considerable en el mercado de automóviles usados.
Promedios de precios por tipo de vehículos:
kable(datos_DT %>% group_by(type) %>% summarise(promedio = round(mean(price), 2)) %>% arrange(desc(promedio)), caption = "Tabla promedios de precios por tipo de coche")| type | promedio |
|---|---|
| pickup | 28606.76 |
| truck | 27723.07 |
| other | 27480.28 |
| coupe | 22432.60 |
| convertible | 19586.13 |
| van | 18546.99 |
| SUV | 17910.63 |
| offroad | 16887.34 |
| bus | 15591.35 |
| hatchback | 15333.43 |
| sedan | 14553.07 |
| wagon | 14475.70 |
| mini-van | 9933.70 |
Modelamiento
Para trabajar con modelos es mejor hacerlo con la estructura data.frame por tanto procedemos a transformar el dataset:
datos_DF <- as.data.frame(datos_DT)
rm(datos_DT)Procedemos a transformar las variable de tipo character a tipo factor para tener una mayor eficiencia:
## 'data.frame': 285424 obs. of 12 variables:
## $ id : num 7.32e+09 7.32e+09 7.32e+09 7.32e+09 7.32e+09 ...
## $ price : num 33590 22590 39590 30990 15000 ...
## $ year : Factor w/ 38 levels "1985","1986",..: 30 26 36 33 29 28 32 35 32 27 ...
## $ manufacturer: Factor w/ 40 levels "acura","alfa-romeo",..: 14 8 8 38 13 14 8 38 8 8 ...
## $ condition : Factor w/ 6 levels "excellent","fair",..: 3 3 3 3 1 3 3 1 3 3 ...
## $ cylinders : Factor w/ 8 levels "10 cylinders",..: 7 7 7 7 6 7 6 6 6 7 ...
## $ fuel : Factor w/ 5 levels "diesel","electric",..: 3 3 3 3 3 3 3 3 3 3 ...
## $ odometer : num 57923 71229 19160 41124 128000 ...
## $ title_status: Factor w/ 6 levels "clean","lien",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ transmission: Factor w/ 3 levels "automatic","manual",..: 3 3 3 3 1 3 3 1 3 3 ...
## $ drive : Factor w/ 3 levels "4wd","fwd","rwd": 1 1 1 1 3 1 1 1 1 3 ...
## $ type : Factor w/ 13 levels "bus","convertible",..: 8 8 8 8 11 8 8 11 8 7 ...
antes de realizar el modelo es importante particionar la data en un conjunto de entrenamiento y otro de test:
set.seed(111)
particion = createDataPartition(y=datos_DF$price,p=0.7, list = F, times = 1)
#Verificamos los tamaños del TRAIN y el TEST
train = datos_DF[particion,]
cat("número de observaciones en el conjunto de entrenamiento: ", dim(train)[1])## número de observaciones en el conjunto de entrenamiento: 199798
test = datos_DF[-particion,]
cat("número de observaciones en el conjunto de test: ", dim(test)[1])## número de observaciones en el conjunto de test: 85626
model <- lm(price~., data = train[,-1])
summary(model)##
## Call:
## lm(formula = price ~ ., data = train[, -1])
##
## Residuals:
## Min 1Q Median 3Q Max
## -90948 -3871 -508 3106 69837
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.749e+04 7.174e+02 38.328 < 2e-16 ***
## year1986 -2.208e+02 7.079e+02 -0.312 0.755059
## year1987 3.866e+03 7.007e+02 5.517 3.45e-08 ***
## year1988 6.057e+00 6.926e+02 0.009 0.993022
## year1989 -1.100e+02 6.781e+02 -0.162 0.871154
## year1990 -2.470e+01 6.710e+02 -0.037 0.970633
## year1991 1.714e+03 6.632e+02 2.584 0.009766 **
## year1992 2.699e+03 6.534e+02 4.130 3.63e-05 ***
## year1993 1.345e+03 6.456e+02 2.083 0.037220 *
## year1994 1.010e+02 6.162e+02 0.164 0.869796
## year1995 1.267e+03 5.898e+02 2.148 0.031721 *
## year1996 9.127e+02 5.846e+02 1.561 0.118457
## year1997 1.678e+03 5.670e+02 2.960 0.003079 **
## year1998 2.102e+03 5.578e+02 3.768 0.000165 ***
## year1999 1.120e+03 5.376e+02 2.083 0.037261 *
## year2000 2.009e+03 5.353e+02 3.753 0.000175 ***
## year2001 2.499e+03 5.269e+02 4.743 2.11e-06 ***
## year2002 2.940e+03 5.225e+02 5.627 1.83e-08 ***
## year2003 3.538e+03 5.176e+02 6.835 8.25e-12 ***
## year2004 3.423e+03 5.147e+02 6.650 2.95e-11 ***
## year2005 4.389e+03 5.128e+02 8.560 < 2e-16 ***
## year2006 4.608e+03 5.114e+02 9.010 < 2e-16 ***
## year2007 5.726e+03 5.102e+02 11.223 < 2e-16 ***
## year2008 6.015e+03 5.093e+02 11.810 < 2e-16 ***
## year2009 7.018e+03 5.115e+02 13.721 < 2e-16 ***
## year2010 8.051e+03 5.096e+02 15.798 < 2e-16 ***
## year2011 9.186e+03 5.084e+02 18.069 < 2e-16 ***
## year2012 1.011e+04 5.075e+02 19.920 < 2e-16 ***
## year2013 1.139e+04 5.066e+02 22.490 < 2e-16 ***
## year2014 1.313e+04 5.069e+02 25.905 < 2e-16 ***
## year2015 1.551e+04 5.069e+02 30.601 < 2e-16 ***
## year2016 1.675e+04 5.070e+02 33.034 < 2e-16 ***
## year2017 1.892e+04 5.068e+02 37.324 < 2e-16 ***
## year2018 2.118e+04 5.071e+02 41.764 < 2e-16 ***
## year2019 2.287e+04 5.085e+02 44.971 < 2e-16 ***
## year2020 2.592e+04 5.098e+02 50.842 < 2e-16 ***
## year2021 2.910e+04 5.885e+02 49.443 < 2e-16 ***
## year2022 6.041e+03 3.120e+03 1.936 0.052845 .
## manufactureralfa-romeo 2.109e+03 3.178e+02 6.635 3.26e-11 ***
## manufactureraston-martin 1.472e+03 2.355e+03 0.625 0.531893
## manufactureraudi 2.487e+03 1.595e+02 15.597 < 2e-16 ***
## manufacturerbmw 1.515e+02 1.441e+02 1.051 0.293402
## manufacturerbuick -3.322e+03 1.771e+02 -18.764 < 2e-16 ***
## manufacturercadillac 7.242e+02 1.680e+02 4.311 1.63e-05 ***
## manufacturerchevrolet -1.691e+03 1.292e+02 -13.091 < 2e-16 ***
## manufacturerchrysler -3.940e+03 1.776e+02 -22.179 < 2e-16 ***
## manufacturerdodge -3.147e+03 1.505e+02 -20.910 < 2e-16 ***
## manufacturerferrari 5.577e+04 1.629e+03 34.236 < 2e-16 ***
## manufacturerfiat -7.861e+03 3.546e+02 -22.167 < 2e-16 ***
## manufacturerford -2.124e+03 1.267e+02 -16.770 < 2e-16 ***
## manufacturergmc -4.051e+02 1.458e+02 -2.778 0.005468 **
## manufacturerharley-davidson -8.766e+03 8.981e+02 -9.760 < 2e-16 ***
## manufacturerhonda -1.016e+03 1.377e+02 -7.380 1.58e-13 ***
## manufacturerhyundai -4.900e+03 1.538e+02 -31.871 < 2e-16 ***
## manufacturerinfiniti -8.723e+02 1.778e+02 -4.907 9.27e-07 ***
## manufacturerjaguar 1.065e+03 2.383e+02 4.468 7.89e-06 ***
## manufacturerjeep -5.375e+02 1.407e+02 -3.821 0.000133 ***
## manufacturerkia -5.847e+03 1.624e+02 -36.014 < 2e-16 ***
## manufacturerland rover -4.296e+03 3.080e+03 -1.395 0.163108
## manufacturerlexus 3.461e+03 1.577e+02 21.943 < 2e-16 ***
## manufacturerlincoln -5.481e+01 1.882e+02 -0.291 0.770903
## manufacturermazda -3.634e+03 1.769e+02 -20.537 < 2e-16 ***
## manufacturermercedes-benz 1.750e+03 1.522e+02 11.496 < 2e-16 ***
## manufacturermercury -3.493e+03 3.378e+02 -10.341 < 2e-16 ***
## manufacturermini -2.250e+03 2.277e+02 -9.880 < 2e-16 ***
## manufacturermitsubishi -7.264e+03 2.062e+02 -35.231 < 2e-16 ***
## manufacturernissan -4.432e+03 1.396e+02 -31.746 < 2e-16 ***
## manufacturerpontiac -6.573e+02 2.682e+02 -2.451 0.014234 *
## manufacturerporsche 9.675e+03 3.081e+02 31.403 < 2e-16 ***
## manufacturerram -1.526e+03 1.487e+02 -10.261 < 2e-16 ***
## manufacturerrover 3.927e+03 2.443e+02 16.074 < 2e-16 ***
## manufacturersaturn -1.600e+03 3.337e+02 -4.796 1.62e-06 ***
## manufacturersubaru -2.287e+03 1.609e+02 -14.214 < 2e-16 ***
## manufacturertesla 1.511e+04 3.980e+02 37.959 < 2e-16 ***
## manufacturertoyota 6.390e+02 1.314e+02 4.864 1.15e-06 ***
## manufacturervolkswagen -4.080e+03 1.575e+02 -25.903 < 2e-16 ***
## manufacturervolvo 9.449e+02 2.067e+02 4.571 4.87e-06 ***
## conditionfair -2.775e+03 1.385e+02 -20.040 < 2e-16 ***
## conditiongood -8.253e+02 4.240e+01 -19.465 < 2e-16 ***
## conditionlike new -8.092e+02 7.245e+01 -11.169 < 2e-16 ***
## conditionnew 1.450e+03 1.746e+02 8.304 < 2e-16 ***
## conditionsalvage -2.085e+03 4.688e+02 -4.448 8.69e-06 ***
## cylinders12 cylinders 6.914e+03 8.350e+02 8.280 < 2e-16 ***
## cylinders3 cylinders -1.341e+04 4.774e+02 -28.079 < 2e-16 ***
## cylinders4 cylinders -9.681e+03 3.005e+02 -32.218 < 2e-16 ***
## cylinders5 cylinders -7.957e+03 3.767e+02 -21.120 < 2e-16 ***
## cylinders6 cylinders -5.984e+03 2.978e+02 -20.097 < 2e-16 ***
## cylinders8 cylinders -2.094e+03 2.968e+02 -7.056 1.72e-12 ***
## cylindersother -7.263e+03 4.328e+02 -16.783 < 2e-16 ***
## fuelelectric -1.427e+04 2.888e+02 -49.413 < 2e-16 ***
## fuelgas -1.277e+04 7.768e+01 -164.454 < 2e-16 ***
## fuelhybrid -1.277e+04 1.584e+02 -80.640 < 2e-16 ***
## fuelother -1.067e+04 9.537e+01 -111.827 < 2e-16 ***
## odometer -5.977e-02 4.179e-04 -143.024 < 2e-16 ***
## title_statuslien -1.094e+03 2.600e+02 -4.207 2.59e-05 ***
## title_statusmissing -3.683e+03 6.810e+02 -5.408 6.37e-08 ***
## title_statusparts only -5.113e+03 1.269e+03 -4.030 5.57e-05 ***
## title_statusrebuilt -4.641e+03 1.180e+02 -39.319 < 2e-16 ***
## title_statussalvage -4.940e+03 1.734e+02 -28.494 < 2e-16 ***
## transmissionmanual 2.280e+03 7.819e+01 29.155 < 2e-16 ***
## transmissionother -4.510e+02 5.733e+01 -7.867 3.66e-15 ***
## drivefwd -2.937e+03 4.487e+01 -65.455 < 2e-16 ***
## driverwd -3.544e+02 5.141e+01 -6.895 5.41e-12 ***
## typeconvertible 6.620e+03 4.411e+02 15.007 < 2e-16 ***
## typecoupe 5.554e+03 4.330e+02 12.827 < 2e-16 ***
## typehatchback 2.954e+03 4.360e+02 6.774 1.26e-11 ***
## typemini-van 4.140e+03 4.485e+02 9.231 < 2e-16 ***
## typeoffroad 8.440e+03 5.755e+02 14.666 < 2e-16 ***
## typeother 6.711e+03 4.331e+02 15.497 < 2e-16 ***
## typepickup 7.707e+03 4.297e+02 17.936 < 2e-16 ***
## typesedan 3.449e+03 4.308e+02 8.005 1.20e-15 ***
## typeSUV 4.417e+03 4.301e+02 10.269 < 2e-16 ***
## typetruck 9.142e+03 4.292e+02 21.298 < 2e-16 ***
## typevan 4.949e+03 4.382e+02 11.294 < 2e-16 ***
## typewagon 4.850e+03 4.387e+02 11.056 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6878 on 199683 degrees of freedom
## Multiple R-squared: 0.7464, Adjusted R-squared: 0.7463
## F-statistic: 5157 on 114 and 199683 DF, p-value: < 2.2e-16
probs <- predict(model, newdata = test[,-1])Notemos que se alcanza un R-cuadrado del 74.63% y un valor de RMSE de:
rmse <- sqrt(sum((test$price-probs)^2)/length(probs))
rmse## [1] 6876.217
cuyo valor se entiende que sea bastante elevado dado que se tratan de 85626 observaciones. Sin embargo, si se revisa los p valores de cada una de las variables y sus distintos niveles se podría mejorar éste modelo si se omiten dichas variables o sus niveles no significativos, es decir, aquellas variables con un p-valor mayor al 5%.
Reales VS predicción
Para una mejor apreciación del ajuste de la predicción a los datos reales, graficaremos las primeras 100 observaciones del data test \(vs\) la predicción para compararlas; y también haremos lo mismo con las 100 últimas observaciones:
serie1 <- data.frame("price" = test$price[1:100], "type" = rep("original", 100))
serie2 <- data.frame("price" = probs[1:100], "type" = rep("predict", 100))
serie1 <- rbind(serie1, serie2)
serie3 <- data.frame("price" = test$price[85527:85626], "type" = rep("original", 100))
serie4 <- data.frame("price" = probs[85527:85626], "type" = rep("predict", 100))
serie2 <- rbind(serie3, serie4)
ggplot(data = serie1,
mapping = aes(x = rep(1:100,2),
y = price,
color = type)) +
geom_line() +
labs(title = "Comparación de las primeras 100 observaciones", x = "Observaciones", y = "Precio")ggplot(data = serie2,
mapping = aes(x = rep(1:100,2),
y = price,
color = type)) +
geom_line() +
labs(title = "Comparación de las últimas 100 observaciones", x = "Observaciones", y = "Precio")#primeras 1000 obervaciones:
qplot(x=test$price[1:1000], y=probs[1:1000], geom=c("point","smooth"), method="lm",
main="Real Vs Prediccion de 1000 observaciones", ylab = "Predicción", xlab = "Real")rm(serie1, serie2, serie3, serie4)