require(dplyr)
require(lubridate)
require(fastDummies)
require(ranger)
require(data.table)
require(tidyverse)
require(tidyr)
require(VIM)
require(DMwR)
Se descarga el conjunto de datos y se guarda en las variables myTrain, myTest y myLabel.
#Por reproducibilidad, se descargan los archivos directamente de la página web de la competición.
#Si se rompen los links, pueden ser extraídos de: https://www.drivendata.org/competitions/7/pump-it-up-data-mining-the-water-table/data/
myTrain <- fread("https://drivendata-prod.s3.amazonaws.com/data/7/public/4910797b-ee55-40a7-8668-10efd5c1b960.csv?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIARVBOBDCYVI2LMPSY%2F20210222%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210222T094545Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=ea5642952302945bedaa9575f80accd501ddf3b21ec782d60b1f84947415418a")
myTest <- fread("https://drivendata-prod.s3.amazonaws.com/data/7/public/702ddfc5-68cd-4d1d-a0de-f5f566f76d91.csv?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIARVBOBDCYVI2LMPSY%2F20210222%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210222T094545Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=0942443c72b4c39d886209712aa314f592b8a984fd786d0d6bd74cefeebc2ae2")
myLabel <- fread("https://drivendata-prod.s3.amazonaws.com/data/7/public/0bf8bc6e-30d0-4c50-956a-603fc693d966.csv?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIARVBOBDCYVI2LMPSY%2F20210222%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210222T094545Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=4ae10b39b2f99fe2c5edc6650d65025cd22cfefc90392e31bb9f702006a54b4b")
# Anexamos la variable objetivo al conjunto de train mediante su "id".
trainIn <- left_join(myTrain, myLabel, by = "id")
# Etiquetamos pertenencia a grupo train o test
trainIn$Train <- 1
myTest$status_group <- "Conjunto myTest"
myTest$Train <- 0
# Sacamos las filas que tienen gps_height a 0 y longitude distinto de 0, puesto que si no tenemos las coordenadas correctas, calcularemos una elevación errónea.
my_coord <- trainIn %>%
filter(gps_height == 0 & longitude != 0) %>%
select(id, gps_height, longitude, latitude) %>%
arrange(id)
# Dividimos el dataset en grupos de 2000 observaciones. Esto es debido a que la API que calcula elevaciones a través de longitudes y latitudes permite un número limitado de consultas a la hora y al día.
n <- 2000
nr <- nrow(my_coord)
splited_df <- split(my_coord, rep(1:ceiling(nr/n), each=n, length.out=nr))
list2env(setNames(splited_df, paste0("coord_", 1:10)), environment())
# Calculamos la elevación para cada uno de los 10 bloques de coordenadas (se necesita un username para ejecutarlo).
coord_1 <- coord_1 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
coord_2 <- coord_2 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
coord_3 <- coord_3 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
coord_4 <- coord_4 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
coord_5 <- coord_5 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
coord_6 <- coord_6 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
coord_7 <- coord_7 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
coord_8 <- coord_8 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
coord_9 <- coord_9 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
coord_10 <- coord_10 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
# Juntamos los splits de coordenadas en un unico dataframe.
coord_height <- do.call("rbind", list(coord_1,
coord_2,
coord_3,
coord_4,
coord_5,
coord_6,
coord_7,
coord_8,
coord_9,
coord_10))
# La función `elevation` devuelve un df de tres columnas, con lo que vamos a hacer un "subset" para extraer la columna que nos interesa y tirar el resto.
coord_height$gps_height <- coord_height$gps_height$elevation_geonames
my_coord_test <- myTest %>%
filter(gps_height == 0 & longitude != 0) %>%
select(id, gps_height, longitude, latitude) %>%
arrange(id)
# Dividimos el dataset en grupos de 2000 observaciones por lo que hemos comentado previamente.
n <- 2000
nr <- nrow(my_coord_test)
splited_df_test <- split(my_coord_test, rep(1:ceiling(nr/n), each=n, length.out=nr))
list2env(setNames(splited_df_test, paste0("coord_", 1:3)), environment())
# Calculamos la elevación para cada uno de los 3 bloques de coordenadas.
coord_1 <- coord_1 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
coord_2 <- coord_2 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
coord_3 <- coord_3 %>%
mutate(gps_height = elevation(latitude = latitude,
longitude = longitude,
username = "username"))
# Juntamos los paquetitos de coordenadas en un gran dataframe
coord_height_test <- do.call("rbind", list(coord_1,
coord_2,
coord_3))
# La función `elevation` devuelve un df de tres columnas, con lo que vamos a hacer un subset para extraer la columna que nos interesa y tirar el resto.
coord_height_test$gps_height <- coord_height_test$gps_height$elevation_geonames
#Unimos los dos datasets (los tenemos bien identificados con el id).
coord_height_full <- rbind(coord_height_train, coord_height_test)
# Le ponemos una etiqueta a las elevaciones sacadas con la API.
coord_height_full$geoname <- TRUE
# Quitamos las columnas que no necesitamos de coord_height
coord_height_full <- coord_height_full[, c(1, 2, 5)]
# Juntamos train + test en un df
tt_df <- rbind(trainIn, myTest)
# Se añaden las elevaciones calculadas con la API mediante un left_join.
tt_df <- tt_df %>% left_join(coord_height_full, by = "id",
suffix = c("", "_geoname"))
tt_df$gps_height <- ifelse((tt_df$gps_height == 0 & tt_df$longitude != 0),
tt_df$gps_height_geoname,
tt_df$gps_height)
# Preparamos los archivos para seguir con su procesado.
myTrain <- tt_df %>%
filter(Train == 1) %>%
select(-c(status_group, Train, geoname, gps_height_geoname))
myTest <- tt_df %>%
filter(Train == 0) %>%
select(-c(status_group, Train, geoname, gps_height_geoname))
Se combinan los conjuntos de myTrain y myLabel mediante la variable id en myTrain y luego se crea una nueva variable myTrainTest combinando myTrain con myTest, exluyendo la variable objetivo del primer conjunto.
# fread lee los "" como NA, así que hay que reemplazarlos manualmente.
myTrain$permit[is.na(myTrain$permit)] <- ""
myTrain$public_meeting[is.na(myTrain$public_meeting)] <- ""
myTest$permit[is.na(myTest$permit)] <- ""
myTest$public_meeting[is.na(myTest$public_meeting)] <- ""
# Anexamos la variable objetivo al conjunto de entrenamiento.
myTrain <- left_join(myTrain, myLabel, by = "id")
# Guardamos la variable objetivo para más tarde.
myVarObj <- myTrain$status_group
# Unimos los conjuntos de entrenamiento y test para asegurarnos que aplicamos las mismas transformaciones a ambos.
myTrainTest <- myTrain %>%
select(-status_group) %>%
bind_rows(myTest)
Se crean nuevas variables a partir de otras, así como se modifican algunas existentes para intentar sacarles un mayor rendimiento.
# Extraemos valor de la variable `date_recorded` puesto que la variable con formato fecha no tiene potencial.
ref_date <- max(ymd(myTrainTest$date_recorded))
myTrainTest$day_count <- as.numeric(ref_date - ymd(myTrainTest$date_recorded))
# Modificamos las variables `region_code` y `district_code` a categóricas para que el modelo no le de un sentido que probablemente no tiene.
myTrainTest$region_code <- paste("RegCode", myTrainTest$region_code, sep = "_")
myTrainTest$district_code <- paste("DistCode", myTrainTest$district_code, sep = "_")
# Creamos las variables mes y año por si la época del año o el mismo año en que se registró tiene alguna relación con el estado de las bombas.
myTrainTest$month <- as.numeric(month(ymd(myTrainTest$date_recorded)))
myTrainTest$year <- as.numeric(year(ymd(myTrainTest$date_recorded)))
# Convertimos la variable de water_quality a dummy.
wq_dum <- dummy_cols(myTrainTest, select_columns = 'water_quality')
wq_dum <- wq_dum[,44:51]
colnames(wq_dum) <- c("wq1", "wq2", "wq3", "wq4", "wq5", "wq6", "wq7", "wq8")
# Convertimos la variable de quantity a dummy.
quan_dum <- dummy_cols(myTrainTest, select_columns = 'quantity')
quan_dum <- quan_dum[,44:48]
colnames(quan_dum) <- c("q1", "q2", "q3", "q4", "q5")
# Anexamos las columnas recientemente creadas.
myTrainTest <- cbind(myTrainTest, wq_dum)
myTrainTest <- cbind(myTrainTest, quan_dum)
myTrainTest <- as.data.table(myTrainTest)
# Convertimos todas las columnas de tipo character a tipo factor.
myTrainTest <- myTrainTest %>%
mutate_if(is.character, as.factor)
# Seleccionamos los nombres de las columnas que son factor.
cfactor <- myTrainTest %>%
select_if(is.factor) %>%
names()
# Creamos un bucle que utiliza los nombres de las variables de tipo factor para anexar columnas de frecuencia para estas variables.
for (i in 1:length(cfactor)) {
myTrainTest[, paste(cfactor[i], 'count', sep = '_') := as.numeric(.N), by = eval(cfactor[i])]
}
# Guardamos los nombres de las columnas que acabamos de crear en una variable.
cCount <- myTrainTest %>%
select(contains("count")) %>%
names()
# Seteamos a -10000 aquellas observaciones cuya frecuencia es inferior a 5 para que el modelo no las considere relevantes.
for (i in 1:length(cCount)) {
myTrainTest[get(cCount[i]) < 5 , c(cCount[i]) := -10000]
}
# Quitamos las columnas de tipo factor puesto que ya no nos sirven.
myTrainTest <- myTrainTest %>%
select_if(negate(is.factor))
Particionado de la variable myTrainTest en train_num y test_num.
# Particionamos en train_num y asignamos le asignamos la variable objetivo.
train_num <- myTrainTest[1:nrow(myTrain),]
train_num$myVarObj <- as.factor(myVarObj)
# Parcicionamos asimismo el test_num.
test_num <- myTrainTest[(nrow(myTrain) + 1):nrow(myTrainTest),]
Modelado de random forest con Ranger.
# Semilla para reproducibilidad.
set.seed(1)
# Modelo Random Forest con Ranger.
my_model <- ranger(
myVarObj ~.,
importance = "impurity",
num.trees = 1000,
mtry = 7,
data = train_num
)
# Hacemos la predicción sobre test_num con el modelo.
my_predict <- predict(my_model, data = test_num)
pred_status_group <- my_predict$predictions
# Creamos el data.frame para la submission.
my_sub <- data.frame(id = test_num$id, status_group = pred_status_group)
# Escribimos el data.frame para la submission en un .csv.
fwrite(my_sub, "./submissions/Modelo_RFR_S20022021_F.csv")