El registro georreferenciado de los hoteles, hostales, albergues y pensiones para los principales puntos de interés turístico español no siempre son compartidos por las instituciones de una forma completa, clara y accesible. En el caso de la ciudad de Valencia, por ejemplo, desde el portal de Turisme se comparte un registro actualizado de dichos establecimientos con datos de contacto y dirección. No obstante, no disponemos de estas mismas direcciones georreferenciadas.
A lo largo de las presentes líneas buscamos explicar al público cómo se han extraído datos útiles para los hoteles de Valencia (aglutinaremos bajo este término a hoteles, hostales, albergues y pensiones) y su paso a R para identificar los registrados administrativamente como tal y georreferenciarlos. Si quieres acceder ya al resultado final (dataset final) de este proyecto puedes descargarlo (en formato shp o csv) desde el siguiente enlace.
En primer lugar, como ya se ha indicado, se va a partir del registro de hoteles que nos proporciona Turisme de la Generalitat Valenciana desde su web. Este mismo conjunto de datos del que hablamos ha sido ya modificado por nosotros con cambios en el nombre de determinados hoteles para hacerlos coincidentes de cara al join que posteriormente llevaremos a cabo con nuestro otro conjunto de datos y con cambios en direcciones que daban lugar a confusión para el proceso de georreferenciación automático que aplicaremos a continuación en R. Una forma sencilla y rápida de ver las anomalías presentes en la georreferenciación es servirse de Leaflet (aunque nosotros nos vamos a servir de este tipo de representación con nuestro conjunto de datos ya bien codificado en estas siguientes líneas, se puede, y debe, hacer uso de él en el mismo proceso de georreferenciación para ir observando errores de localizaciones, ausencias o duplicaciones que nos devuelve la función geo). Así pues, ese mismo conjunto de datos corregido es el que descargamos a continuación e intentamos georreferenciar sirviéndonos de la función geo() del paquete tidygeocoder. Esta función nos dará las coordenadas de los hoteles tomando como referencia OpenStreetMap.
hotelesregistrados <- "https://www.dropbox.com/s/f7ghqrw3rok3jn6/lista_hoteles_Valencia_46_250_ALL_02_05_2022.csv?dl=1"
destfile <- "~/lista_hoteles_Valencia_46_250_ALL_02_05_2022.csv"
download.file(hotelesregistrados, destfile)
hotelesvlctur <- import("~/lista_hoteles_Valencia_46_250_ALL_02_05_2022.csv")
hotelesvlctur <-  hotelesvlctur[which(!duplicated(hotelesvlctur$Nombre)), ]
hotelesvlctur$Dirección <- paste0(hotelesvlctur$Dirección, ", Valencia ")

datos <- geo(
  address = hotelesvlctur$Dirección, method = "osm",
  lat = lat,
  long = long
)
Pese a las correcciones pertinentes en el registro que estamos manejando y gracias a las cuales corregimos previamente errores futuros de georreferenciación, todavía existen localizaciones duplicadas. Las identificamos en el código inferior y vamos a servirnos de Google My Maps como herramienta auxiliar de georreferenciación. Una vez extraídas, podemos guardar estas ubicaciones duplicadas en un archivo csv (como se ve en la línea de código comentada) e importarlo a GMM. Google My Maps, sin embargo, no nos va a permitir descargar en un archivo las coordenadas obtenidas tras georreferenciar las direcciones con su servicio.
names(datos)[1] <- "Dirección"
hotelesgeorreferenciados <- cbind(hotelesvlctur, datos)[,c(6,13,12,33,34)]
hotelesgeorreferenciados$coord <- paste (hotelesgeorreferenciados$lat, hotelesgeorreferenciados$long)
hotelesgeorreferenciados2 <- hotelesgeorreferenciados[duplicated(hotelesgeorreferenciados$coord)|duplicated(hotelesgeorreferenciados$coord, fromLast=TRUE),]
head(hotelesgeorreferenciados2)
                          Nombre
6  AC HOTEL VALENCIA BY MARRIOTT
9         AD HOC MONUMENTAL 1881
11              PENSIÓN ALICANTE
12                 HOTEL ALKAZAR
21              BARCELÓ VALENCIA
22                 HOTEL BELERET
                                             Dirección    CP      lat
6              AVENIDA DE FRANCIA, 67, GRAO, Valencia  46023 39.45815
9                         CARRER DE BOIX, 4, Valencia  46003 39.47746
11                       CALLE DE RIBERA, 2, Valencia  46002 39.46830
12 PLAZA DEL AYUNTAMIENTO, 11, CIUDAD VIEJA, Valencia  46002 39.47075
21             AVENIDA DE FRANCIA, 11, GRAO, Valencia  46023 39.45815
22          CALLE CAMPAMENTO, 76, BENIMÀMET, Valencia  46035 39.49966
         long                 coord
6  -0.3346157 39.4581509 -0.3346157
9  -0.3720122  39.477461 -0.3720122
11 -0.3758831 39.4682957 -0.3758831
12 -0.3765557 39.4707498 -0.3765557
21 -0.3346157 39.4581509 -0.3346157
22 -0.4269970  39.4996622 -0.426997
#Guardamos en el código inferior e importamos a Google My Maps
#write.csv(hotelesgeorreferenciados2,"~/listahotelesvalenciacomprobar.csv", row.names = FALSE) 
Las nuevas coordenadas correctas para los hoteles que se debían concretar con exactitud y así lo hemos hecho con Google My Maps son las siguientes:
hotelesgeorreferenciados <- hotelesgeorreferenciados %>% 
  mutate(coord = ifelse(as.character(Nombre) == "AC HOTEL VALENCIA BY MARRIOTT", '39.45886-0.34393', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "AD HOC MONUMENTAL 1881", '39.47707-0.37207', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "PENSIÓN ALICANTE", '39.46888-0.37586', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "HOTEL ALKAZAR", '39.4689-0.37431', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "BARCELÓ VALENCIA", '39.46087-0.35258', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "HOTEL BELERET", '39.50056-0.42511', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "PLAZA REDONDA BRUGADA HOME", '39.4732-0.37696', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "BUHO HOUSE", "39.47448-0.37138", coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "CASUAL DE LAS ARTES VALENCIA", '39.46709-0.35582', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "CASUAL VINTAGE VALENCIA", '39.47134-0.37632', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "CATALONIA EXCELSIOR", '39.4714-0.37587', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "DWO VALENCIA", '39.46481-0.34687', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "COOL LOFS CENTER VALENCIA", '39.47406-0.38438', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "BOUTIQUE CREATIVE ROOMS",'39.47157-0.37709', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "DORMAVALENCIA HOSTEL", '39.4709-0.35486', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "HOSTAL EL CID", '39.47331-0.37706', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "HOTELITO BOUTIQUE VALENCIA ESTACIÓN", '39.46263-0.37985', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "ILUNION AQUA 3", '39.45535-0.34292', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "ILUNION AQUA 4", '39.45594-0.34578', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "JAMAICA", '39.46664-0.3782', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "JERO ROOMS", '39.46818-0.37566', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "APARTAMENTOS LÍBERE VALENCIA JARDÍN BOTÁNICO", '39.47412-0.38618', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "MARQUÉS HOUSE HOTEL 4* SUP", '39.473-0.3754', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "MD DESIGN HOTEL - PORTAL DEL REAL", '39.47697-0.37238', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "MELIA VALENCIA", '39.49472-0.40081', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "NEST STYLE VALENCIA", '39.46616-0.35295', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "NH CIUDAD DE VALENCIA", '39.46276-0.34339', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "NH VALENCIA LAS ARTES", '39.45572-0.3573', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "NH VALENCIA LAS CIENCIAS", '39.45538-0.35817', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "NWT CENTRAL STATION APARTMENTS", '39.4666-0.37827', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "ONE SHOT PALACIO REINA VICTORIA 04", '39.47021-0.37533', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "OTTOH CHARM STAY", '39.47466-0.37147', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "PARADOR DE EL SALER", '39.35971-0.32661', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "PETIT PALACE PLAZA DE LA REINA", '39.47312-0.37533', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "SILKEN PUERTA VALENCIA", '39.46847-0.35562', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "HOSTAL RESIDENCIAL RR", '39.50042-0.42559', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "SERCOTEL SOROLLA PALACE", '39.49617-0.40132', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "PENSIÓN UNIVERSAL", '39.4705-0.37514', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "HOTEL VALENCIA CENTER", '39.46017-0.34948', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "VALENCIAFLATS CIUDAD DE LAS CIENCIAS", '39.45538-0.35817', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "HOTEL VILLACARLOS", '39.46639-0.35521', coord))%>% 
  mutate(coord = ifelse(as.character(Nombre) == "YOU & CO. SALER BEACH BOUTIQUE", '39.38323-0.33266', coord))

hotelesgeorreferenciados$long <- sub(".*-", "", hotelesgeorreferenciados$coord)
hotelesgeorreferenciados$long <- as.numeric(paste0("-",hotelesgeorreferenciados$long))
hotelesgeorreferenciados$lat<- regmatches(hotelesgeorreferenciados$coord,gregexpr(".*-",hotelesgeorreferenciados$coord,perl=TRUE))
hotelesgeorreferenciados$lat <- as.numeric(sub("-", "", hotelesgeorreferenciados$lat))
hotelesgeorreferenciados <- hotelesgeorreferenciados[,c(1:5)]
hotelesgeorreferenciados$Nombre = toupper(hotelesgeorreferenciados$Nombre)
Pese a la necesidad de reformular algunas direcciones exactas proporcionadas por el registro de Turisme con tal de evitar fallos en la georreferenciación realizada con la función geo() a través de las direcciones de OpenStreetMap y la necesidad, por otro lado, de acudir de manera complementaria a los servicios de Google My Maps, conviene señalar que la función geo(), según hemos podido comprobar, georreferencia correctamente con una exactitud de cerca del 70%. Llegados a este punto, nos encontramos en disposición de representar gráficamente los hoteles de Valencia sobre un mapa. Elegimos un punto de referencia para el mapa (el cual georreferenciamos también con la función geo()).Nos parece interesante que el mapa a representar sea en relación a los códigos postales de Valencia capital. Recomendamos encarecidamente lo escrito por el profesor Francisco Goerlich en el siguiente documento que trata en gran manera sobre la accesibilidad pública y gratuita a un mapa de códigos postales. La forma de poder representar dicho mapa con Leaflet (fruto de la inexistencia de listados de códigos postales y mapas basados en estos de forma pública) ha pasado por extraer la información necesaria de este repositorio de Github, el cual conserva información que en un momento dado fue pública y que ya no lo es.
datos2 <- geo(
  address = "Plaza del ayuntamiento, Ciudad Vieja, Valencia", method = "osm",
  lat = lat,
  long = long
)

#CUENTALE DEL GITHUB QUE VIENE
url <- "https://github.com/inigoflores/ds-codigos-postales/archive/refs/heads/master.zip"
download(url, dest="master.zip", mode="wb") 
unzip ("master.zip", exdir = "~")


cpval <- import("~/ds-codigos-postales-master/data/codigos_postales_municipios.csv")
cp <- readOGR("~/ds-codigos-postales-master/data/VALENCIA.geojson")
OGR data source with driver: GeoJSON 
Source: "C:\Users\Nax\Documents\ds-codigos-postales-master\data\VALENCIA.geojson", layer: "VALENCIA"
with 488 features
It has 4 fields
cpval <- cp[grep("^460", cp$COD_POSTAL),]
geo <- sptable(cpval)[,c(1,5,6)] 
means <- aggregate(geo[,2:3], geo[1], mean) 
colnames(means) <- c("ID","lat","lon")
means$ID <- cpval$COD_POSTAL

cpval@data$lat <- means$lat
cpval@data$lon <- means$lon

mapahoteles <- leaflet() %>% addProviderTiles(providers$Esri.NatGeoWorldMap) %>% 
  addPolygons(data=cpval,fillColor=c("red","green","blue","yellow"), 
              fillOpacity = 0.2,weight = 2, stroke = TRUE,smoothFactor = 0.5, color = "black", opacity = 1) %>% 
  addMarkers(hotelesgeorreferenciados$long,hotelesgeorreferenciados$lat,label = as.character(hotelesgeorreferenciados$Nombre),labelOptions = labelOptions(noHide = F),clusterOptions = markerClusterOptions()) %>% 
  setView(datos2$long,datos2$lat ,zoom=11.8) 

mapahoteles
#Si quisiésemos guardarlo:
#saveWidget(mapahoteles, "~//mapahoteles.html", selfcontained = FALSE)
Una vez tenemos nuestros datos correctamente georreferenciados, hemos llevado a cabo un proceso de web scraping para extraer algunos datos de los hoteles valencianos desde junio 2022 y para los diez meses siguientes. Se han extraído datos de Booking para los fines de semana (sábado y domingo) tomando como base el precio para una persona de una habitación no compartida. A ese precio total de fin de semana, posteriormente lo trataremos como precio medio de la noche en fin de semana una vez importemos los datos a R. Se han scrapeado datos para 43 fines de semana concretamente. Algunas variables que contienen esos 43 conjuntos son valoración, distrito, precio total en fin de semana, web del hotel o número de comentarios (alguna de las cuales descartaremos más adelante). Octoparse nos permite algunas alteraciones mínimas de los conjuntos una vez aplicado el web scraping para extraer esos datos. Por ejemplo, crear una nueva columna con la fecha de esa recogida de datos. No obstante, será luego en R cuando hagamos un poco más de data cleaning. Descargamos, por tanto, en las siguientes líneas de codigo los 43 conjuntos de datos extraídos por web scraping mediante Octoparse. Lo hacemos de una manera intuitiva y rápida para hacerlo de una sola vez.
rm(list=setdiff(ls(), "hotelesgeorreferenciados"))

url <- 'https://www.dropbox.com/s/9ixozhih0bp8u2a/Fines_de_semana_desde_junio.zip?dl=1'
download(url, dest="dataset.zip", mode="wb") 
unzip ("dataset.zip", exdir = "~")

filenames <- list.files(path="~/Fines_de_semana_desde_junio",
                        pattern=".*xlsx")

names <- sub("\\..*", "", filenames)

for(i in names){
  filepath <- file.path("~/Fines_de_semana_desde_junio",paste(i,".xlsx",sep=""))
  assign(i, import(filepath))
}
Arreglamos algunos conjuntos a los que hay que aplicar determinados cambios para poder efectuar el bind_rows() entre todos ellos, variables a las que queremos cambiar el tipo de dato y el nombre de las columnas, por ejemplo. Una manera sencilla de indicarle que queremos la unión de esos 43 dataframes es señalándole que queremos todos los dataframes de nuestro environment excepto el de hotelesgeorreferenciados que antes generamos y el cual seguimos necesitando.
`hotelesvlc3-5diciembre2022`$Precio <- as.character(`hotelesvlc3-5diciembre2022`$Precio)
`hotelesvlc30-1agosto2022`$Fecha  <- as.character(`hotelesvlc30-1agosto2022`$Fecha)  

dfs = sapply(.GlobalEnv, is.data.frame)[-4]

total <- bind_rows(mget(names(dfs)[dfs])) 
total <- total[,c(1,3,5,7,8, 9)]
total$Precio <- sub("\\.", "", total$Precio)
total <- total %>% 
  mutate(Precio = (as.numeric(Precio)/2))

total <- total %>% 
  mutate(Precio = round(Precio, digits=1))

total$d8eab2cf7f <- sub("\\ comentarios", "", total$d8eab2cf7f)
total$d8eab2cf7f <- as.numeric(sub("\\.", "", total$d8eab2cf7f))

total$b5cd09854e <- as.numeric(sapply(total$b5cd09854e, gsub, pattern = ",", replacement= "."))

names(total)[1] <- "Nombre"
names(total)[2] <- "Distrito"
names(total)[3] <- "Valoración"
names(total)[4] <- "Número de comentarios"

total$Nombre = toupper(total$Nombre)
En principio, estamos ya en disposición de hacer el left_join() con el registro de hoteles verificados y georreferenciados, con tal de dotar de coordenadas a estos datos obtenidos de Booking y descartar también hoteles que no sean tal, sino que se traten más bien de alojamientos de particulares, los cuales abundan cada vez más en plataformas como Booking.
hotelesconcoord <- left_join(total, hotelesgeorreferenciados, by = "Nombre") %>% drop_na(Dirección)
hotelesconcoord$Fecha <- as.Date(hotelesconcoord$Fecha, '%d/%m/%Y')
hotelesconcoord <- hotelesconcoord %>% 
  mutate(Distrito = ifelse(as.character(Distrito) == "Valencia", "Poblados del Oeste, Valencia", Distrito))
Por último, podemos echar un rápido vistazo descriptivo a lo obtenido de forma gráfica. Pasamos a ver los hoteles con mayor precio medio en fin de semana según los datos recogidos, así como la evolución temporal de este precio. Cabe destacar que, de cara a un análisis exploratorio de estos datos muy visual y dinámico, hemos llevado a cabo también una app de Qlik Sense para representar de forma general toda esta información. Como apunte final, conviene destacar, en vías de que el usuario pueda tratar este conjunto de datos públicos como él quiera, que ante los datos faltantes de algunos hoteles para algunas fechas (casos en los que no existe disponibilidad de fechas para ese hotel en esas fechas), se ha optado por no interceder en esa ausencia. Los motivos de esto han sido diversos: puede ser interesante para alguien analizar la falta de oferta para algunas fechas, o por otro lado, a lo mejor alguien quiere complementar esas ausencias con predicciones de lo que hubiese sido el precio si hubiese estado disponible ese hotel en la fecha de extracción de estos datos o, por poner otro ejemplo, sustituir esa ausencia por el precio medio del resto de fines de semana. Ya que el uso de estos datos puede depender de la motivación analítica del usuario, dejamos a libre disposición de cada uno que trate esta realidad como mejor desee.
mediaporfecha <- aggregate(hotelesconcoord$Precio, list(hotelesconcoord$Fecha), FUN=mean) 
mediadecadahotel <- aggregate(hotelesconcoord$Precio, list(hotelesconcoord$Nombre), FUN=mean) 
mediadecadahotel <- mediadecadahotel[order(-mediadecadahotel$x),]
mediadecadahotel <- mediadecadahotel %>% 
  mutate(x = round(x, digits=1))

ggplot(data= mediadecadahotel[c(1:10),], aes(reorder(Group.1,x),x, fill = x)) + geom_bar(position = 'dodge', stat='identity')+geom_text(aes(label=x), vjust=0, hjust=0)+
  scale_fill_viridis_c() + coord_flip()+ggtitle("TOP 10 de hoteles por precio medio/día en fin de semana")+
 theme(legend.position="none")+xlab("Hotel")+ylab("Precio en euros")+
 theme(plot.caption = element_text(face = "italic"),
    axis.title = element_text(colour = "blue4"),
    plot.title = element_text(face = "bold",
        colour = "blue4")) +labs(caption = "Fuente: Booking.")

ggplot(data= mediaporfecha, aes(x=Group.1, y=x)) + geom_line()+
ggtitle("Evolución del precio medio en fin de semana de los hoteles")+
  theme(legend.position="none")+xlab("Hotel")+ylab("Precio en euros")+
  theme(plot.caption = element_text(face = "italic"),
        axis.title = element_text(colour = "blue4"),
        plot.title = element_text(face = "bold",
                                  colour = "blue4")) +labs(caption = "Fuente: Booking.")+
  ylim(0, 500)

Si deseamos guardar el dataset finalmente elaborado:
write.csv(hotelesconcoord,"~/valenciahoteles.csv", row.names = FALSE)

#O en formato shp:

hotelesgeorreferenciadosshp <- st_as_sf(hotelesconcoord, coords = c("lat", "long"), crs="+proj=longlat +datum=WGS84")
dir.create(file.path("~/shpvlchoteles"))
st_write(hotelesgeorreferenciadosshp, "~/shpvlchoteles/shpvlchoteles.shp", driver = "ESRI Shapefile")
Para finalizar, es relevante comentar qué sucede para otras ciudades de claro interés turístico en lo respectivo a disponibilidad de información pública de hoteles georreferenciados. En el caso de Barcelona, estaríamos ante el ejemplo de datos públicos, sencillos de adquirir y reutilizar por el usuario (el posible pero que se podría señalar es la imposibilidad de constatar si se tratan de unos datos en periódica actualización o no). Para el caso de Madrid, los datos se encuentran georreferenciados pero se comparten en XML, dificultando el tratamiento de los datos, respecto al ejemplo anterior de Barcelona donde se compartían tanto en csv como json. En un formato XML, el tratamiento de los datos una vez descargados es un poco más complejo. Mostramos de forma simple, para finalizar este documento, cómo podríamos disponer de estos datos de Madrid arreglados en unas pocas líneas con el fin de disponer de los hoteles y sus coordenadas:
hotelesmadrid <- "https://www.dropbox.com/s/p20imrgwel6e2ps/alojamientos_v1_es.xml?dl=1"
destfile <- "~/alojamientos_v1_es.xml"
download.file(hotelesmadrid, destfile)

hmadrid <- xmlToDataFrame("~/alojamientos_v1_es.xml")
doc = xmlParse("~/alojamientos_v1_es.xml")
dd = xmlToDataFrame(getNodeSet(doc, "//serviceList//service//basicData"))
hmadrid$basicData <- dd$name

hmadrid <- hmadrid[grep("Madrid", hmadrid$geoData),]
hmadrid <- hmadrid[- grep("Las Rozas", hmadrid$geoData),]
hmadrid$geoData <- sub(".*Spain", "", hmadrid$geoData)     
hmadrid$geoData <- sub("Madrid*", "", hmadrid$geoData) 

hmadrid$long <- sub(".*-", "", hmadrid$geoData)
hmadrid$long <- paste0("-",hmadrid$long)

hmadrid$lat<- regmatches(hmadrid$geoData,gregexpr(".*-",hmadrid$geoData,perl=TRUE))
hmadrid$lat <- sub("-*", "", hmadrid$lat)