data_file = read.csv("realestate_texas.csv")
str(data_file)
## 'data.frame': 240 obs. of 8 variables:
## $ city : chr "Beaumont" "Beaumont" "Beaumont" "Beaumont" ...
## $ year : int 2010 2010 2010 2010 2010 2010 2010 2010 2010 2010 ...
## $ month : int 1 2 3 4 5 6 7 8 9 10 ...
## $ sales : int 83 108 182 200 202 189 164 174 124 150 ...
## $ volume : num 14.2 17.7 28.7 26.8 28.8 ...
## $ median_price : num 163800 138200 122400 123200 123100 ...
## $ listings : int 1533 1586 1689 1708 1771 1803 1857 1830 1829 1779 ...
## $ months_inventory: num 9.5 10 10.6 10.6 10.9 11.1 11.7 11.6 11.7 11.5 ...
data_file$date = as.Date(paste(data_file$year, data_file$month, "01", sep = "-"))
str(data_file$date)
## Date[1:240], format: "2010-01-01" "2010-02-01" "2010-03-01" "2010-04-01" "2010-05-01" ...
library(moments)
quant_var = c("sales", "volume", "median_price", "listings", "months_inventory")
summary(data_file[quant_var]) # minimo, massimo, quartili, mediana, media
## sales volume median_price listings
## Min. : 79.0 Min. : 8.166 Min. : 73800 Min. : 743
## 1st Qu.:127.0 1st Qu.:17.660 1st Qu.:117300 1st Qu.:1026
## Median :175.5 Median :27.062 Median :134500 Median :1618
## Mean :192.3 Mean :31.005 Mean :132665 Mean :1738
## 3rd Qu.:247.0 3rd Qu.:40.893 3rd Qu.:150050 3rd Qu.:2056
## Max. :423.0 Max. :83.547 Max. :180000 Max. :3296
## months_inventory
## Min. : 3.400
## 1st Qu.: 7.800
## Median : 8.950
## Mean : 9.193
## 3rd Qu.:10.950
## Max. :14.900
apply(data_file[quant_var], 2, function(x){any(x) <= 0}) # controllo zeri
## Warning in any(x): coercizione argomento di tipo 'double' in logico
## Warning in any(x): coercizione argomento di tipo 'double' in logico
## Warning in any(x): coercizione argomento di tipo 'double' in logico
## Warning in any(x): coercizione argomento di tipo 'double' in logico
## Warning in any(x): coercizione argomento di tipo 'double' in logico
## sales volume median_price listings
## FALSE FALSE FALSE FALSE
## months_inventory
## FALSE
apply(data_file[quant_var], 2, function(x){exp(mean(log(x)))}) # media geometrica
## sales volume median_price listings
## 1.768815e+02 2.686046e+01 1.305956e+05 1.585449e+03
## months_inventory
## 8.880933e+00
apply(data_file[quant_var], 2, var) # varianza
## sales volume median_price listings
## 6.344300e+03 2.772707e+02 5.135730e+08 5.665690e+05
## months_inventory
## 5.306889e+00
apply(data_file[quant_var], 2, sd) # deviazione standard
## sales volume median_price listings
## 79.651111 16.651447 22662.148687 752.707756
## months_inventory
## 2.303669
apply(data_file[quant_var], 2, function(x){(sd(x)/mean(x))*100}) # coefficiente di variazione
## sales volume median_price listings
## 41.42203 53.70536 17.08218 43.30833
## months_inventory
## 25.06031
apply(data_file[quant_var], 2, function(x){max(x)-min(x)}) # range
## sales volume median_price listings
## 344.000 75.381 106200.000 2553.000
## months_inventory
## 11.500
apply(data_file[quant_var], 2, IQR) # differenza interquartile
## sales volume median_price listings
## 120.0000 23.2335 32750.0000 1029.5000
## months_inventory
## 3.1500
apply(data_file[quant_var], 2, skewness) # asimmetria
## sales volume median_price listings
## 0.71810402 0.88474203 -0.36455288 0.64949823
## months_inventory
## 0.04097527
apply(data_file[quant_var], 2, kurtosis) # curtosi (centrata sul 3)
## sales volume median_price listings
## 2.686824 3.176987 2.377038 2.208210
## months_inventory
## 2.825552
cat_var = c("city", "date")
date_city_freq_dist = lapply(cat_var, function(x){
n = table(data_file[[x]]) # frequenza assoluta
f = prop.table(n) # frequenza relativa
data_file_out = data.frame(
variable_name = names(n),
ni = as.vector(n),
fi = as.vector(f)
)
data_file_out$Ni = cumsum(data_file_out$ni) # frequenza cumulata
data_file_out$Fi = cumsum(data_file_out$fi) # frequenza relativa cumulata
return(data_file_out)
})
La variabile con maggior variabilità è “volume”, come si può evincere dal coefficiente di variazione (indice ottimale per confrontare variabili su scale differenti). In altre parole la variabile volume è caratterizzata da valori che si discostano maggiormente dal suo valor medio (elevato grado di dispersione). La variabile con maggior asimmetria è “volume”, proprietà osservabile dal risultato dell’indice skewness. Ciò indica la possibile presenza di outlier, o comunque la possibilità che i valori siano sbilanciati verso una delle due parti della distribuzione. Nel caso specifico della variabile volume, l’indice misurato risulta essere positivo, per cui la distribuzione è asimmetrica positiva (valori più bassi più frequenti, media > mediana > moda).
sales_classes = cut(data_file$sales,
breaks = c(1,100,200,300,400,500),
labels = c("1-100", "101-200", "201-300", "301-400", "401-500"),
right = TRUE,
include.lowest = TRUE)
s_ni = table(sales_classes)
s_fi = prop.table(s_ni)
s_Ni = cumsum(s_ni)
s_Fi = cumsum(s_fi)
sales_freq_dist = data.frame(Class = names(s_ni),
ni = as.numeric(s_ni),
fi = round(as.numeric(s_fi), 4),
Ni = as.numeric(s_Ni),
Fi = round(as.numeric(s_Fi), 4)
)
sales_barplot = barplot(height = sales_freq_dist$fi,
names.arg = sales_freq_dist$Class,
col = "lightblue",
main = "Distribuzione delle vendite per classe",
xlab = "Classi",
ylab = "Frequenza relativa",
border = "black",
ylim = c(0,0.6)
)
text(x = sales_barplot,
y = sales_freq_dist$fi,
label = round(sales_freq_dist$fi, 3),
pos = 3
)
Gini.index = function(x){
ni = table(x)
fi = table(x)/length(x)
fi2 = fi^2
J = length(table(x))
Gini = 1-sum(fi2)
norm_Gini = Gini/((J-1)/J)
return(norm_Gini)
}
Gini.index(data_file$sales)
## [1] 0.998379
Gini.index(sales_classes)
## [1] 0.7796441
La distribuzione di frequenze mostra che la classe 101-200 registra (in termini di frequenza assoluta e frequenza relativa) i valori più alti, seguita poi dalla classe 201-300. Da ciò si evince che mensilmente il numero delle vendite si concentra nelle classi intermedie, e sono meno frequenti le classi agli estremi della distribuzione (infatti sono rari i casi in cui si registrano più di 400 vendite per mese). L’indice di Gini calcolato sulla variabile sales è 0.78, ovvero prossimo a 1. Tale valore indica una disuguaglianza significativa tra le varie classi (eterogeneità elevata), in accordo con ciò che si osserva dalla distribuzione di frequenze.
p_Beaumont = sum(data_file$city == "Beaumont")/nrow(data_file)
p_July = sum(data_file$month == "7")/nrow(data_file)
p_Dec2012 = sum(data_file$date == "2012-12-01")/nrow(data_file)
I risultati sono in accordo con le distribuzioni di frequenze.
data_file$average_price = (data_file$volume*10^6)/data_file$sales
sales_listings_ratio = data_file$sales/data_file$listings
price_score = data_file$median_price/max(data_file$median_price)
time_score = 1/data_file$months_inventory
data_file$efficacy = (sales_listings_ratio + price_score + time_score)/3
La variabile efficacy misura l’efficacia degli annunci tenendo conto di alcune variabili del data set, e producendo un risultato compreso tra 0 e 1 (per cui 0 rappresenta la totale inefficacia degli annunci, mentre 1 l’efficacia del 100%). Le variabili tenute in considerazione sono sales, listings, median_price e months_inventory. La variabile sales tiene conto del numero di immobili venduti, per cui un maggior numero di immobili rappresenta una maggior efficacia, tuttavia è necessario rapportarlo alla variabile listings (numero di annunci attivi in quell’intervallo temporale) dal momento in cui un rapporto più elevato suggerisce che la maggior parte degli immobili presenti sul mercato sono stati venduti. La variabile median_price invece rappresenta una sorta di grado di difficoltà di vendita, infatti se il rapporto tra annunci venduti e annunci disponibili è pari, l’efficacia può considerarsi maggiore per un prezzo mediano maggiore. In ultimo la quantità di mesi trascorsi per vendere tutti gli annunci attivi, ossia la variabile months_inventory, per la quale si ha che un valore minore rappresenta una maggior efficienza di vendita.
summary(data_file$efficacy)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.2085 0.2925 0.3151 0.3243 0.3517 0.5300
var(data_file$efficacy)
## [1] 0.002914934
sd(data_file$efficacy)
## [1] 0.05399013
(sd(data_file$efficacy)/mean(data_file$efficacy))*100
## [1] 16.64704
skewness(data_file$efficacy)
## [1] 1.077353
kurtosis(data_file$efficacy)
## [1] 4.77061
Dai risultati si evince che in media l’efficacia è del 34.4% (molto simile alla mediana), con un’efficacia minima del 20.8% e massima del 53.0%. La variabilità dei dati relativi a tale variabile è del 16,6% (piuttosto contenuta), e la distribuzione risulta essere asimmetrica positiva leptocurtica.
library(dplyr)
##
## Caricamento pacchetto: 'dplyr'
## I seguenti oggetti sono mascherati da 'package:stats':
##
## filter, lag
## I seguenti oggetti sono mascherati da 'package:base':
##
## intersect, setdiff, setequal, union
city_summary = data_file %>%
group_by(city) %>%
summarise(sales_mean = mean(sales),
sales_sd = sd(sales),
volume_mean = mean(volume),
volume_sd = sd(volume),
median_price_mean = mean(median_price),
median_price_sd = sd(median_price),
listings_mean = mean(listings),
listings_sd = sd(listings),
months_inventory_mean = mean(months_inventory),
months_inventory_sd = sd(months_inventory))
year_summary = data_file %>%
group_by(year) %>%
summarise(sales_mean = mean(sales),
sales_sd = sd(sales),
volume_mean = mean(volume),
volume_sd = sd(volume),
median_price_mean = mean(median_price),
median_price_sd = sd(median_price),
listings_mean = mean(listings),
listings_sd = sd(listings),
months_inventory_mean = mean(months_inventory),
months_inventory_sd = sd(months_inventory))
month_summary = data_file %>%
group_by(month) %>%
summarise(sales_mean = mean(sales),
sales_sd = sd(sales),
volume_mean = mean(volume),
volume_sd = sd(volume),
median_price_mean = mean(median_price),
median_price_sd = sd(median_price),
listings_mean = mean(listings),
listings_sd = sd(listings),
months_inventory_mean = mean(months_inventory),
months_inventory_sd = sd(months_inventory))
library(ggplot2)
library(tidyr)
s_l_city_plot = city_summary %>%
select(city, sales_mean, listings_mean) %>%
pivot_longer(cols = c(sales_mean, listings_mean),
names_to = "variable",
values_to = "value")
ggplot(s_l_city_plot, aes(x = city, y = value, fill = variable)) +
geom_col(position = position_dodge(width = 0.8)) +
geom_text(aes(label = round(value, 1)),
position = position_dodge(width = 0.8),
vjust = -0.3,
size = 3.5) +
labs(title = "Vendite/Annunci per città",
x = "Città",
y = "Media di Vendite/Annunci") +
scale_fill_manual(values = c("sales_mean" = "lightblue",
"listings_mean" = "darkred"),
labels = c("Annunci", "Vendite")) +
theme_classic(base_size = 10) +
theme(
axis.text.x = element_text(angle = 0, vjust = 0.5),
legend.title = element_blank(),
plot.title = element_text(face = "bold")
)
ggplot(city_summary, aes(x = city, y = median_price_mean, fill = median_price_mean)) +
geom_col() +
geom_errorbar(aes(ymin = median_price_mean - median_price_sd,
ymax = median_price_mean + median_price_sd),
width = 0.2,
color = "black") +
geom_text(aes(label = round(median_price_mean, 0)),
vjust = -3,
size = 3.5) +
scale_fill_gradient(low = "lightgreen", high = "darkgreen") +
labs(title = "Prezzo Mediano per città",
x = "Città",
y = "Prezzo Mediano Medio") +
scale_y_continuous(breaks = seq(0, 160000, by=40000), limits = c(0, 180000)) +
theme_classic(base_size = 10) +
theme(
axis.text.x = element_text(angle = 0, vjust = 0.5),
legend.position = "none",
plot.title = element_text(face = "bold")
)
ggplot(city_summary, aes(x = city, y = months_inventory_mean, fill = months_inventory_mean)) +
geom_col() +
geom_errorbar(aes(ymin = months_inventory_mean - months_inventory_sd,
ymax = months_inventory_mean + months_inventory_sd),
width = 0.2,
color = "black") +
geom_text(aes(label = round(months_inventory_mean, 1)),
vjust = -0.5,
size = 5) +
scale_fill_gradient(low = "lightpink", high = "darkred") +
scale_y_continuous(breaks = seq(0, 14, by=2), limits = c(0, 14)) +
labs(title = "Tempistiche di Vendita per città",
x = "Città",
y = "Mesi impiegati per vendere totale immobili") +
theme_classic(base_size = 10) +
theme(
axis.text.x = element_text(angle = 0, vjust = 0.5),
legend.position = "none",
plot.title = element_text(face = "bold")
)
ggplot(year_summary, aes(x = year, y = sales_mean)) +
geom_line(color = "red", size = 0.8) +
geom_point(color = "darkred", size = 3) +
geom_text(aes(label = round(sales_mean, 0)),
vjust = -2, size = 3.5) +
labs(title = "Andamento temporale delle Vendite",
x = "Anno",
y = "Media delle Vendite") +
scale_y_continuous(breaks = seq(100, 250, by=50), limits = c(100, 260)) +
theme_bw(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 0, vjust = 0.5)
)
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
ggplot(year_summary, aes(x = year, y = median_price_mean)) +
geom_line(color = "lightblue", size = 0.8) +
geom_point(color = "darkblue", size = 3) +
geom_text(aes(label = round(median_price_mean, 0)),
vjust = -2, size = 3.5) +
labs(title = "Andamento temporale del Prezzo Mediano",
x = "Anno",
y = "Media del Prezzo Mediano") +
scale_y_continuous(breaks = seq(100000, 160000, by=20000), limits = c(100000, 160000)) +
theme_bw(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 0, vjust = 0.5)
)
ggplot(month_summary, aes(x = month, y = sales_mean)) +
geom_line(color = "violet", size = 1) +
geom_point(color = "darkviolet", size = 3) +
geom_text(aes(label = round(sales_mean, 1)),
vjust = -2, size = 3) +
labs(
title = "Vendite Mensili",
x = "Mese",
y = "Media delle Vendite"
) +
scale_x_continuous(breaks = seq(0, 12, by=1), limits = c(1, 12)) +
scale_y_continuous(breaks = seq(100, 250, by=50), limits = c(100, 280)) +
theme_bw(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 0, vjust = 0.5)
)
ggplot(month_summary, aes(x = month)) +
geom_line(aes(y = median_price_mean, color = "Prezzo Mediano Medio"), size = 1) +
geom_line(aes(y = sales_mean * 1000, color = "Media Vendite"), size = 1) +
geom_text(aes(y = median_price_mean, label = round(median_price_mean, 0), color = "Prezzo Mediano Medio"),
vjust = -0.5, size = 3) +
geom_text(aes(y = sales_mean * 1000, label = round(sales_mean, 0), color = "Media Vendite"),
vjust = -0.5, size = 3) +
scale_color_manual(
name = "Variabili:",
values = c("Prezzo Mediano Medio" = "blue",
"Media Vendite" = "red")) +
scale_y_continuous(
name = "Media del Prezzo Mediano",
breaks = seq(125000, 250000, 50000),
limits = c(120000, 250000),
sec.axis = sec_axis(~ . / 1000, name = "Media delle Vendite")) +
labs(
title = "Prezzi vs Vendite",
x = "Mese") +
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 13)) +
theme_bw(base_size = 12) +
theme(plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 0, vjust = 0.5),
legend.position = "bottom")
month_plot = month_summary %>%
select(month, median_price_mean, volume_mean) %>%
pivot_longer(cols = c(median_price_mean, volume_mean),
names_to = "variable",
values_to = "value")
max_price = max(month_summary$median_price_mean)
max_volume = max(month_summary$volume_mean)
scale_factor = max_price / max_volume
ggplot(month_plot, aes(x = month)) +
geom_line(aes(y = ifelse(variable == "volume_mean", value * scale_factor, value),
color = variable), size = 1) +
geom_point(aes(y = ifelse(variable == "volume_mean", value * scale_factor, value),
color = variable), size = 3) +
geom_text(aes(y = ifelse(variable == "volume_mean", value * scale_factor, value),
label = round(value, 1), color = variable),
vjust = -1, size = 3, check_overlap = FALSE) +
scale_color_manual(
name = "Variabili",
values = c("median_price_mean" = "blue", "volume_mean" = "red"),
labels = c("Prezzo Mediano Medio", "Ricavato Totale Medio delle Vendite (Milioni USD)")) +
scale_y_continuous(
name = "Prezzo Mediano Medio (USD)",
sec.axis = sec_axis(~ . / scale_factor,
name = "Ricavato Totale Medio delle Vendite (Milioni USD)")) +
scale_x_continuous(breaks = 1:12) +
labs(title = "Prezzo Mediano vs Ricavato Totale delle Vendite Mensili",
x = "Mese") +
theme_bw(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 0, vjust = 0.5),
legend.position = "bottom")
ggplot(data_file, aes(x = city, y = median_price, fill = city)) +
geom_boxplot() +
labs(
title = "Distribuzione del Prezzo Mediano per Città",
x = "Città",
y = "Prezzo Mediano (USD)") +
theme_classic(base_size = 10) +
theme(
axis.text.x = element_text(angle = 0, hjust = 0.5),
legend.position = "none",
plot.title = element_text(face = "bold"))
Le città presentano livelli del prezzo mediano differenti. Alcune città mostrano box più compatti (maggior omogeneità del mercato), mentre altre sono caratterizzate da outlier e una più ampia dispersione (indice di maggiore variabilità del prezzo).
ggplot(data_file, aes(x = city, y = volume, fill = city)) +
geom_boxplot(outlier.colour = "black", outlier.size = 1.5) +
labs(
title = "Distribuzione del Ricavato Totale delle Vendite per Città",
x = "Città",
y = "Ricavato delle Vendite (Milioni USD)") +
theme_classic(base_size = 10) +
theme(
legend.position = "none",
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 0, vjust = 0.5))
Le marcate differenze tra le distribuzioni evidenziano come in alcune città si concentri una quota maggiore del ricavato totale, non necessariamente per prezzi più alti, ma anche per numero di vendite (o viceversa). Inoltre box più lunghi indicano maggior volatilità del fatturato.
ggplot(data_file, aes(x = factor(year), y = volume)) +
geom_boxplot(fill = "lightblue", outlier.colour = "black", outlier.size = 1.5) +
labs(
title = "Distribuzione del Ricavato Totale delle Vendite per Anno",
x = "Anno",
y = "Ricavato delle Vendite (Milioni USD)") +
theme_classic(base_size = 10) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 0, hjust = 0.5))
Nel tempo si osserva un progressivo allungamento delle distribuzioni, infatti la dispersione del ricavato totale è più ampia, e uno spostamento verso l’alto (anche la mediana aumenta). Questi risultati sono indici di espansione disomogenea e crescita strutturale del mercato.
ggplot(data_file, aes(x = month, y = sales, fill = city)) +
geom_col(position = position_dodge(width = 0.8)) +
scale_x_continuous(breaks = 1:12) +
labs(
title = "Totale Vendite Mensili per Città",
x = "Mese",
y = "Numero di Vendite",
fill = "Città") +
theme_classic(base_size = 10) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 0, vjust = 0.5))
sales_month_city = data_file %>%
group_by(month, city) %>%
summarise(total_sales = sum(sales), .groups = "drop")
ggplot(sales_month_city,
aes(x = factor(month), y = total_sales, fill = city)) +
geom_col() +
scale_x_discrete(drop = FALSE) +
labs(
title = "Totale delle Vendite Mensili per Città",
x = "Mese",
y = "Numero Totale di Vendite",
fill = "Città") +
theme_classic(base_size = 10) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 0))
Per ciascuna città è possibile osservare andamenti stagionali simili, con un aumento delle vendite durante il periodo primaverile/estivo, e un decremento nel periodo autunnale/invernale. In alcuni casi la variazione è più marcata, mentre in altre città il livello delle vendite si mantiene pressoché costante.
ggplot(sales_month_city,
aes(x = factor(month), y = total_sales, fill = city)) +
geom_col(position = "fill") +
scale_y_continuous(labels = scales::percent) +
scale_x_discrete(drop = FALSE) +
labs(
title = "Distribuzione Percentuale delle Vendite Mensili per Città",
x = "Mese",
y = "Quota Percentuale delle Vendite",
fill = "Città") +
theme_classic(base_size = 10) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 0))
Tramite normalizzazione si può verificare con quale peso incidono le variazioni stagionali per ciascuna città sul totale delle vendite, indipendentemente dalla crescita del numero. Si osserva infatti come alcune città contribuiscano in particolar modo alla crescita del numero totale di vendite in funzione del mese, mentre altre risultano essere meno soggette a tale fenomeno.
ggplot(data_file, aes(x = date, y = sales, color = city)) +
geom_line(size = 1) +
labs(
title = "Andamento Storico delle Vendite Immobiliari in Texas",
x = "Data",
y = "Numero di Vendite",
color = "Città") +
scale_x_date(date_labels = "%Y-%m",
date_breaks = "3 months",
limits = as.Date(c("2010-01-01", max(data_file$date)))) +
theme_bw(base_size = 10) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5),
axis.text.x = element_text(angle = 60, hjust = 1),
legend.position = "bottom")
Il confronto temporale mostra che le città non seguono traiettorie identiche nel tempo, bensì alcune sono caratterizzate da una crescita più regolare e continua, mentre altre presentano fasi di accelerazione più marcata. Questo risultato indica che il ciclo immobiliare non è perfettamente sincronizzato a livello geografico, ma risente delle dinamiche locali.
Il mercato immobiliare analizzato mostra un quadro generalmente espansivo. Le vendite crescono in modo piuttosto costante nel tempo, sia in senso stretto (numero di transazioni), sia in senso lato (totale del ricavato), suggerendo un settore con domanda sostenuta e senza evidenti shock recessivi nel periodo coperto dal dataset. La stagionalità risulta evidente: i volumi di vendita tendono a concentrarsi nel periodo primaverile ed estivo, con attenuazioni dell’attività nei mesi più freddi. Questo pattern stagionale orienta le imprese a pianificare pricing e promozioni tenendo conto dei picchi annuali.
Le città invece si differenziano sia per intensità delle vendite, che per livelli di prezzo, infatti alcune piazze mostrano medie di vendita più alte, mentre altre si distinguono per prezzi mediani superiori. La combinazione tra volume e prezzo mediano non è uniforme tra le città, di fatto è possibile osservare come in alcuni casi il volume sia alto, ma il prezzo mediano risulti basso (tante vendite, ma a prezzi ridotti), mentre in altri casi è il volume a risultare basso, con un prezzo mediano più elevato (poche vendite, ma con prezzi alti). Questo suggerisce che le strategie di mercato efficaci non siano trasferibili da una città all’altra, a meno che non si effettuino degli adattamenti. Inoltre, la relazione tra prezzi e numero di vendite non appare inversa; al contrario le due variabili si muovono in modo tendenzialmente concorde nel medio periodo, come evidenziato dai grafici mensili e annuali. Tale dinamica suggerisce che la domanda sia sufficientemente forte da assorbire livelli di prezzo crescenti, evitando compressioni del volume.
La variabile listings e l’indicatore months_inventory confermano un mercato relativamente attivo. Le tempistiche di vendita non risultano eccessivamente lunghe e non seguono trend di deterioramento. La stabilità della variabie inventory indica l’esistenza di un equilibrio tra domanda e offerta (nonostante i prezzi crescenti): l’offerta non appare strutturalmente insufficiente, né sovrabbondante, e contribuisce a consolidare la crescita del mercato.
Nel lungo periodo è possibile osservare un’evoluzione più robusta del prezzo mediano rispetto al numero di vendite. Ciò è coerente con il ciclo espansivo immobiliare: prima la domanda aumenta in quantità, poi i prezzi seguono e si consolidano. Non emergono crolli o inversioni, e le città che partono da livelli di prezzo inferiori mostrano recuperi più marcati nel tempo, riducendo il divario con le città storicamente più care.
Una lettura congiunta dei risultati supporta quindi tre punti chiave. Primo: il mercato del periodo analizzato non sembra essere soggetto ad alcuna speculazione, in quanto volumi, prezzi e inventory si muovono coerentemente e senza segnali di stress. Secondo: la stagionalità ha un ruolo non trascurabile e può essere sfruttata in termini di ottimizzazione dei tempi di immissione dell’offerta. Terzo: la segmentazione geografica è determinante per comprendere le performance del mercato immobiliare; trattare il “Texas” come un unico mercato porterebbe a perdite di informazione e alla definizione di strategie non ottimali.
Alla luce di quanto sopra, si formulano alcune raccomandazioni operative. Nel breve periodo conviene pianificare annunci e campagne di vendita in prossimità della stagione più attiva. Nel medio periodo invece ha senso concentrare l’interesse di investimento nelle città in cui la crescita combinata di prezzo e volume è più marcata, segnale di domanda resiliente. Infine, nel lungo periodo, monitorare attentamente l’indicatore inventory potrà fornire segnali anticipatori di cambiamento del ciclo: un aumento persistente dei tempi di vendita senza crescita dell’offerta indicherebbe indebolimento della domanda, mentre una compressione dei tempi con offerta stagnante potrebbe anticipare nuove spinte al rialzo dei prezzi.