Librerias
librerie = c(
"jsonlite",
"httr",
"data.table",
"scales",
"ggplot2",
"leaflet",
"dplyr",
"MASS",
"ggplot2",
"RColorBrewer",
"reticulate",
"lime",
"splines",
"tidyverse",
"data.table",
"tseries",
"mgcv",
"regclass",
"glmnet",
"coefplot"
)
Install_And_Load <- function(packages) {
k <- packages[!(packages %in% installed.packages()[,"Package"])];
if(length(k))
{install.packages(k, repos='https://cran.rstudio.com/');}
for(package_name in packages)
{library(package_name,character.only=TRUE, quietly = TRUE);}
}
Install_And_Load(librerie)
Per accedere alle API di idealista necessitiamo di ottenere le credenziali. Una volta ottenute le carichiamo in R. Inoltre, prepariamo anche altri parametri necessari al collegamento con il sito.
# parametri in input
# nuove credenziali
consumer_key = load("consumer_key.rda")
consumer_secret = load("consumer_secret.rda")
# save(consumer_key, file = "consumer_key.rda")
# save(consumer_secret, file = "consumer_secret.rda")
#Use basic authentication
secret <- jsonlite::base64_enc(paste(consumer_key, consumer_secret, sep = ":"))
req <- httr::POST("https://api.idealista.com/oauth/token",
httr::add_headers(
#"Authorization" = paste("Basic", gsub("n", "", secret)),
"Authorization" = paste("Basic", secret, sep = " "),
"Content-Type" = "application/x-www-form-urlencoded;charset=utf-8"
),
body = "grant_type=client_credentials"
)
token <- paste("Bearer", httr::content(req)$access_token)
Prepariamo i parametri in input che andranno a creare il link con la quale verrà eseguita la richiesta. Il nostro obiettivo è quello di scaricare tutti gli annunci delle case in vendita a Malaga. Come posizione è stata inserita la stazione Maria Zambrano ed stata impostata una distanza massima di 10 km. Come dimensione minima del locale è stato prefissato il valore di 30 per escludere eventuali annunci di garage.
#url user parameters
x = '36.71145256718129'
y = '-4.4288958904720355'
# x = '45.643170'
# y = '13.790524'
maxItems = '10000'
distance = '10000'
type = 'homes'
op = 'sale'
minprice = '30001'
maxprice = '200000000'
minsize = '30'
maxsize = '10000'
#url fixed parameters
site = 'https://api.idealista.com/3.5/es/search?'
# site = 'https://api.idealista.com/3.5/it/search?'
loc = 'center='
country = '&country=es'
# country = '&country=it'
maxitems = '&maxItems=50'
pages = '&numPage='
dist = '&distance='
property = '&propertyType='
operation = '&operation='
pricefrom = '&minPrice='
priceto = '&maxPrice='
misize = '&minSize='
masize = '&maxSize='
chalet = '&chalet=0'
Ora inoltreremo la richiesta di ricerca a idealista. Abbiamo un
limite mensile di massimo 100 richieste (pagina = 100), e
di massimo una richiesta al secondo (Sys.sleep(1)). Nel
ciclo a ogni giro viene generata una richiesta che differisce unicamente
sul numero della pagina dei risultati, andando così a prelevare tutti i
dati di una singola ricerca.
Una volta scaricati i dati ed estratti dal JSON otteremo delle liste che a sua volta dovranno essere estratte e immagazzinate in un dataframe. Il problema emerge in quanto all’interno della lista sono presenti sotto liste e, oltretutto, non per ogni annuncio. Allora bisogna creare una matrice che per ogni richiesta abbia il numero di colonne pari alla grandezza massima di variabili differenti per un’osservazione e successivamente riempita. Infine i dati vengono uniti ai dati delle richieste precedenti, aggiungendo le eventuali nuove colonne al database già esistente.
pagina = 100
for(z in 1:pagina)
{
print(z)
# prepara l'url
url <- paste(site, loc, x, ',', y, country, maxitems, pages, z, dist, distance,
property, type, operation, op, pricefrom, minprice, priceto, maxprice,
misize, minsize, masize, maxsize, sep = "")
# invia la richiesta a idealista
res <- httr::POST(url, httr::add_headers("Authorization" = token))
# estrai il contenuto dal JSON
cont_raw <- httr::content(res)
# Va a cercare l'item con più colonne
indexColMax = sapply(1:length(cont_raw[[1]]), function(x) cont_raw[[1]][[x]] %>% names() %>% length) %>% which.max
colNames = cont_raw[[1]][[indexColMax]] %>% names()
# Creo una matrice vuota dove imagazzinare i valori
m = matrix(NA, nrow = length(cont_raw[[1]]), ncol = length(colNames))
colnames(m) = colNames
for(r in 1:length(cont_raw[[1]]))
{
for(c in 1:length(cont_raw[[1]][[r]]))
{
# nel caso l'elemento della lista sia una sotto lista o df vado a spacchettarlo aggiungendo colonne
if(length(cont_raw[[1]][[r]][[c]])>1)
{
# non si può fare in un unico caso
for(i in 1:length(cont_raw[[1]][[r]][[c]]))
{
# se la colonna della sottolista non è già stata aggiunta lo faccio
if(is.null(names(cont_raw[[1]][[r]][[c]])))
{
cont_raw[[1]][[r]][[c]] = cont_raw[[1]][[r]][[c]][[1]]
}
if(!names(cont_raw[[1]][[r]][[c]])[i] %in% colNames)
{
colNames = c(colNames, names(cont_raw[[1]][[r]][[c]])[i])
m = cbind(m, rep(NA,length(cont_raw[[1]]))) # aggiunta della colonna
colnames(m) = colNames
}
}
# inserisco i dati della sottolista
for(k in 1:length(cont_raw[[1]][[r]][[c]]))
m[r,names(cont_raw[[1]][[r]][[c]])[k]] = cont_raw[[1]][[r]][[c]][[k]]
}else{
tryCatch(
{
m[r,names(cont_raw[[1]][[r]][c])] = ifelse(length(cont_raw[[1]][[r]][[c]][[1]])>1,
cont_raw[[1]][[r]][[c]][[1]][[1]],
cont_raw[[1]][[r]][[c]][[1]])
},
error = function(e) print(e, z, r, c))
}
}
}
data = m %>% data.frame() %>% tibble()
# debug
print(c(z,minprice,maxprice,data %>% dim))
# merge database
if(z == 1)
{
d = data
}else
{
data[setdiff(names(d), names(data))] <- NA
d[setdiff(names(d), names(data))] <- NA
d = bind_rows(d, data)
}
Sys.sleep(1)
}
saveRDS(data, "dati idealista")
Per evitare di fare ulteriori richieste al sito vengono prelevati i dati già raccolti in precedenza e il codice precedente non viene eseguito.
data = readRDS(file = "data/dati")
Viene eseguita una pulizia dei dati grossolana, modificando principalmente il tipo di variabile.
data.frame(1:dim(data)[2],data %>% names)
# pulizia dei dati
data$floor[data$floor == "bj"] = 0
indexNumeric = c(1,4,5,6,9,11,12,19,20,23,29,30,43)
data = data %>% mutate_at(indexNumeric, as.numeric)
## Warning: There were 2 warnings in `mutate()`.
## The first warning was:
## ℹ In argument: `floor = .Primitive("as.double")(floor)`.
## Caused by warning:
## ! NA introdotti per coercizione
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 1 remaining warning.
indexFactor = c(7,8,14,15,16,17,18,26,31,39)
data = data %>% mutate_at(indexFactor, as.factor)
indexLogic = c(10,21,25,28,33:38,41,42)
data = data %>% mutate_at(indexLogic, as.logical)
data
Istogramma del piano delle case nella quale si trovano.
ggplot(data %>% subset(!is.na(floor)), aes(x = floor)) +
geom_histogram(bins = max(data$floor,na.rm = T)) +
scale_x_continuous(breaks = pretty_breaks(max(data$floor,na.rm = T)))
Grafico a barre del prezzo medio delle case in rapporto al piano nella
quale si trovano.
ggplot(data %>%
subset(!is.na(floor)) %>%
group_by(floor) %>%
summarise(price = mean(price)),
aes(x = floor, y = price)) +
geom_col() +
scale_x_continuous(breaks = pretty_breaks(max(data$floor,na.rm = T)),,
labels = scales::unit_format(unit = "€"))
Boxplot del prezzo medio delle case in funzione del piano nella quale si trovano.
ggplot(data %>% mutate(floor = as.factor(floor)) %>% subset(!is.na(floor)),
aes(x = floor, y = price)) +
geom_boxplot() +
scale_y_log10(labels = scales::unit_format(unit = "€"))
Distribuzione della dimensione delle case.
ggplot(data %>% subset(!is.na(size)), aes(x = size)) +
geom_histogram(bins = 100) +
scale_x_continuous(trans = log10_trans())
ggplot(data, aes(x = floor, y = price)) +
geom_point() +
geom_smooth(method = "gam",
formula = y ~ bs(x)) +
scale_y_log10(labels = scales::unit_format(unit = "€")) +
scale_x_continuous(n.breaks = max(data$floor,na.rm = T))
## Warning: Removed 1305 rows containing non-finite values (`stat_smooth()`).
## Warning: Removed 1305 rows containing missing values (`geom_point()`).
Prezzo delle case in funzione della loro dimensione. Gli assi non sono proporzionali ma sono logaritmici.
ggplot(data, aes(x = size, y = price)) +
geom_point() +
geom_smooth() +
scale_x_log10() +
scale_y_log10(labels = scales::unit_format(unit = "€"))
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
Prezzo delle case in funzione al numero di bagni che la casa possiede.
ggplot(data %>% mutate(bathrooms = as.factor(bathrooms)),
aes(x = bathrooms, y = price)) +
geom_boxplot() +
scale_x_discrete(breaks = pretty_breaks(max(data$bathrooms))) +
scale_y_log10(labels = scales::unit_format(unit = "€"))
Mappa del prezzo delle case nelle diverse zone della città. La mappa è interattiva, cliccando sui singoli pallini comparirà una box con ulteriori dati sulla casa.
pal = with(data, colorFactor(brewer.pal(10,"RdYlGn"), -price))
dfPopup = data %>%
mutate(popup_info = paste("Prezzo della casa: ", price, " $", "</br>",
"Superficie: ", size, "</br>",
"Piano: ", floor, "</br>",
"Numero di bagni: ", bathrooms, "</br>",
"Numero di camere: ", rooms, "</br>",
"Zona: ", neighborhood, "</br>",
"Distanza: ", distance, "</br>"))
leaflet() %>%
addTiles() %>%
addCircleMarkers(data = dfPopup,
lat = ~ latitude,
lng = ~ longitude,
radius = ~ 2,
opacity = .7,
color = ~ pal(-price),
popup = ~ popup_info)
Correlogramma delle variabili più frequenti.
data %>%
select_if(is.numeric) %>%
dplyr::select(!c(parkingSpace,distance,propertyCode,parkingSpace,subtitle)) %>%
na.roughfix() %>%
cor %>%
corrplot::corrplot(method = "number",
hclust.method = "ward.D2",
diag = F,
type = "upper",
order = "hclust",
number.cex = .6)