Introduzione

L’azienda Texas Realty Insights vuole analizzare le tendenze del mercato immobiliare in Texas per supportare decisioni strategiche di vendita e ottimizzazione delle inserzioni. Il dataset contiene 240 osservazioni mensili relative a 4 città del Texas nel periodo 2010–2014, con informazioni su vendite, volumi, prezzi mediani e annunci attivi.

Step 1 — Analisi delle variabili

Il dataset si compone di 8 variabili, classificate come segue:

Verifichiamo la struttura del dataset:

glimpse(realestate)
## Rows: 240
## Columns: 8
## $ city             <chr> "Beaumont", "Beaumont", "Beaumont", "Beaumont", "Beau…
## $ year             <dbl> 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010,…
## $ month            <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5,…
## $ sales            <dbl> 83, 108, 182, 200, 202, 189, 164, 174, 124, 150, 150,…
## $ volume           <dbl> 14.162, 17.690, 28.701, 26.819, 28.833, 27.219, 22.70…
## $ median_price     <dbl> 163800, 138200, 122400, 123200, 123100, 122800, 12430…
## $ listings         <dbl> 1533, 1586, 1689, 1708, 1771, 1803, 1857, 1830, 1829,…
## $ months_inventory <dbl> 9.5, 10.0, 10.6, 10.6, 10.9, 11.1, 11.7, 11.6, 11.7, …

Controlliamo che il panel sia bilanciato:

table(realestate$city, realestate$year)
##                        
##                         2010 2011 2012 2013 2014
##   Beaumont                12   12   12   12   12
##   Bryan-College Station   12   12   12   12   12
##   Tyler                   12   12   12   12   12
##   Wichita Falls           12   12   12   12   12

Tutte le celle contengono 12 osservazioni (i 12 mesi), confermando che il dataset è perfettamente bilanciato.

Step 2 — Indici di posizione, variabilità e forma

Variabili quantitative

Calcoliamo gli indici sintetici per le cinque variabili numeriche significative attraverso la funzione describe() del pacchetto psych:

describe(realestate[, c("sales", "volume", "median_price", "listings", "months_inventory")])
##                  vars   n      mean       sd    median   trimmed      mad
## sales               1 240    192.29    79.65    175.50    184.97    82.28
## volume              2 240     31.01    16.65     27.06     29.17    16.16
## median_price        3 240 132665.42 22662.15 134500.00 133607.81 24092.25
## listings            4 240   1738.02   752.71   1618.50   1677.36   879.92
## months_inventory    5 240      9.19     2.30      8.95      9.20     2.15
##                       min       max     range  skew kurtosis      se
## sales               79.00    423.00    344.00  0.71    -0.34    5.14
## volume               8.17     83.55     75.38  0.88     0.15    1.07
## median_price     73800.00 180000.00 106200.00 -0.36    -0.64 1462.84
## listings           743.00   3296.00   2553.00  0.65    -0.81   48.59
## months_inventory     3.40     14.90     11.50  0.04    -0.20    0.15

Per ottenere quartili e IQR , non inclusi in describe() , utilizziamo summary() e sapply():

summary(realestate[, c("sales", "volume", "median_price", "listings", "months_inventory")])
##      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
sapply(realestate[, c("sales", "volume", "median_price", "listings", "months_inventory")], IQR)
##            sales           volume     median_price         listings 
##         120.0000          23.2335       32750.0000        1029.5000 
## months_inventory 
##           3.1500

Calcoliamo infine il coefficiente di variazione (CV = sd/media), che useremo per confrontare la variabilità tra variabili con scale diverse essendo una misura adimensionale:

sapply(realestate[, c("sales", "volume", "median_price", "listings", "months_inventory")],
       function(x) sd(x) / mean(x))
##            sales           volume     median_price         listings 
##        0.4142203        0.5370536        0.1708218        0.4330833 
## months_inventory 
##        0.2506031

Variabili qualitative e temporali

Per city, year e month non ha senso calcolare media o varianza: ricostruiamo invece le distribuzioni di frequenza.

table(realestate$city)
## 
##              Beaumont Bryan-College Station                 Tyler 
##                    60                    60                    60 
##         Wichita Falls 
##                    60
table(realestate$year)
## 
## 2010 2011 2012 2013 2014 
##   48   48   48   48   48
table(realestate$month)
## 
##  1  2  3  4  5  6  7  8  9 10 11 12 
## 20 20 20 20 20 20 20 20 20 20 20 20

Tutte le distribuzioni risultano uniformi, 60 osservazioni per ognuna delle quattro città, 48 osservazioni per ognuno dei 5 anni coperti dal dataset e 20 osservazioni per ognuno dei dodici mesi dell’anno, coerentemente con la natura di panel bilanciato del dataset.

Commenti

  • La variabile median_price mostra media (132.665 $) e mediana (134.500 $) molto vicine: la lieve prevalenza della mediana sulla media e la skewness negativa (−0.36) indicano una distribuzione quasi simmetrica, leggermente asimmetrica a sinistra.
  • Le variabili sales, volume e listings presentano media maggiore della mediana e skewness positiva, segnalando code destre dovute alla presenza nel dataset di città di dimensione molto diversa.
  • months_inventory è la variabile più simmetrica (skewness ≈ 0.04), coerentemente con la sua natura di indicatore già “normalizzato” (un rapporto, non un aggregato).
  • Confrontando i CV, volume è la variabile con maggiore variabilità relativa (53.7%), mentre median_price la più stabile (17.1%).

Step 3 — Variabilità e asimmetria

Variabile più variabile

Come già detto confrontare le deviazioni standard delle variabili avrebbe poco senso, poiché esse hanno unità di misura e ordini di grandezza diversi (dollari, mesi, milioni…). Utilizziamo quindi il coefficiente di variazione, adimensionale, già calcolato nello Step 2:

sapply(realestate[, c("sales", "volume", "median_price", "listings", "months_inventory")],
       function(x) sd(x) / mean(x))
##            sales           volume     median_price         listings 
##        0.4142203        0.5370536        0.1708218        0.4330833 
## months_inventory 
##        0.2506031

La variabile con maggiore variabilità relativa è volume con CV = 0.537, seguita da listings (0.43) e sales (0.41). All’estremo opposto, median_price è la più stabile (0.17).

Questo risultato è coerente con la natura dei dati: il volume di vendita, dato dal prodotto tra numero di transazioni e prezzi, vista la coesistenza nel dataset di città di scala molto diversa (da Beaumont a Bryan-College Station), “raccoglie” al suo interno la variabilità di entrambi i fattori che lo compongono che possono variare in sincronia spostandosi da una città all’altra.

Variabile più asimmetrica

Per confrontare l’asimmetria utilizziamo il valore assoluto della skewness:

sapply(realestate[, c("sales", "volume", "median_price", "listings", "months_inventory")],
       function(x) abs(skewness(x)))
##            sales           volume     median_price         listings 
##       0.71810402       0.88474203       0.36455288       0.64949823 
## months_inventory 
##       0.04097527

La variabile più asimmetrica è nuovamente volume (|skew| = 0.88), con asimmetria positiva (coda destra): la media (31.0) supera la mediana (27.1), confermando il pattern.

In base alle soglie convenzionali:

  • |skew| < 0.5 → distribuzione approssimativamente simmetrica
  • 0.5 ≤ |skew| < 1 → moderata asimmetria
  • |skew| ≥ 1 → forte asimmetria

volume, sales e listings ricadono nella fascia di moderata asimmetria positiva, mentre median_price e months_inventory risultano quasi simmetriche.

Osservazione conclusiva

volume risulta prima in entrambe le classifiche, sia per variabilità che per asimmetria. Non è una coincidenza: la coda destra (pochi valori molto alti, dovuti alle città grandi) inflaziona sia la deviazione standard sia la skewness. Le due caratteristiche sono manifestazioni dello stesso fenomeno : la coesistenza nel dataset di realtà urbane di scala economica eterogenea.

Step 4 — Distribuzione in classi e indice di Gini

Dati i valori trovati nel punto precedente scelgo come variabile target da suddividere il ‘median price’, la sua skewness è tra le più basse, indice di maggiore simmetria quindi la divisione in classi permette una ripartizione abbastanza uniforme delle osservazioni. Inoltre racconta una utilità maggiore dal punto di vista del mercato immobiliare per capire il posizionamento delle inserzioni.

Costruzione delle classi

Il range di “median_price” è [73.800-180.000] , volendo dividere in 5 classi ognuna misurerebbe (180.000-73.800)/5 = 21.240. Per comodità arrotondo a 25.000 l’ampiezza di ogni classe.

realestate <- realestate %>%
  mutate(price_class = cut(median_price,
                           breaks = c(60000, 85000, 110000, 135000, 160000, 185000),
                           include.lowest = TRUE,
                           labels = c("60-85k", "85-110k", "110-135k", "135-160k", "160-185k")))


freq_ass <- table(realestate$price_class)
freq_ass
## 
##   60-85k  85-110k 110-135k 135-160k 160-185k 
##        2       47       74       96       21
freq_rel <- prop.table(freq_ass)
round(freq_rel, 3)
## 
##   60-85k  85-110k 110-135k 135-160k 160-185k 
##    0.008    0.196    0.308    0.400    0.088

Visualizzazione grafica

Il grafico più adatto a rappresentare le varie classi è quello a barre:

ggplot(realestate,aes(x=price_class))+
  geom_bar(fill="steelblue",color= "white")+
  labs(title="Distribuzione del prezzo mediano in classi",
       x="Classe di prezzo (USD)",
       y="Frequenza assoluta") +
  theme_minimal()

Indice di eterogeneità di Gini

L’indice di eterogeneità di Gini misura quanto le osservazioni sono distribuite uniformemente tra le classi:

\[G = 1 - \sum_{i=1}^{k} f_i^2\] L’indice può variare tra 0 (caso di massima omogeneità quindi osservazioni tutte in una classe) e 1-1/k.

k<-length(freq_rel)
G<- 1-sum(freq_rel^2)
G_max<-(k-1)/k
G_norm<-G/G_max

cat("Numero di classi (k):", k, "\n")
## Numero di classi (k): 5
cat("Indice di Gini (G):", round(G, 4), "\n")
## Indice di Gini (G): 0.6989
cat("Gini massimo (G_max):", round(G_max, 4), "\n")
## Gini massimo (G_max): 0.8
cat("Gini normalizzato (G'):", round(G_norm, 4), "\n")
## Gini normalizzato (G'): 0.8736

Commento conclusivo

La divisione in classi della variabile ‘median_price’ permette di osservare la prevalenza delle inserzioni nella classe [135k-160k] che coprono il 40% del dataset e più in generale una concentrazione negli intervalli centrali che lascia agli estremi poco meno del 10% delle inserzioni. La skewness calcolata nello step 2 anticipava questo risultato raccontando una distribuzione quasi simmetrica con una coda leggermente più lunga sul lato sinistro( skewness -0,36).

Step 5 — Calcolo delle probabilità

Nel nostro dataset la probabilità di estrarre una riga riferita alla città di Beaumont è data da:

sum(realestate$city == "Beaumont") / nrow(realestate)
## [1] 0.25

il valore ottenuto di 0,25 conferma quanto visto nello step 1, le quattro città sono equamente rappresentate con 60 osservazioni per una, pertanto equiprobabili (25% l’una).

La probabilità di estrarre un’inserzione del mese di luglio è data da:

sum(realestate$month == 7) / nrow(realestate)
## [1] 0.08333333

il valore trovato di 0,083 equivale a 1/12 a conferma del fatto che tutti i mesi dell’anno come visto in precedenza sono equamente presenti nel dataset.

La probabilità di estrarre inserzioni relative a dicembre 2012 è data invece da:

sum(realestate$month==12 & realestate$year==2012)/nrow(realestate)
## [1] 0.01666667

il valore di 0,0167 corrisponde a 4/240 , quindi 1/60.
Nello Step 2 abbiamo visto che le inserzioni sono equamente distribuite per anno e per mese quindi,essendo indipendenti i singoli eventi,
P(dicembre ∩ 2012)= P(dicembre) * P(2012),
con P(dicembre)=1/12 e P(2012)=1/5
P(dicembre ∩ 2012)= 1/12 * 1/5 = 1/60 come appunto risulta.

Step 6 — Creazione di nuove variabili

Con le variabili a disposizione vado a fare feature engineering creando due variabili utili alla mia analisi del mercato immobiliare texano: il prezzo medio per immobile e un indicatore adatto a verificare l’efficacia degli annunci.

Prezzo medio per immobile

Per calcolare il prezzo medio per immobile divido il valore totale delle vendite per il numero effettivo di vendite, visto che il primo valore è espresso in milioni di dollari dovrò moltiplicarlo adeguatamente per rendere la misura corretta.

realestate$mean_price <- (realestate$volume * 1000000) / realestate$sales


summary(realestate$mean_price)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   97010  132939  156588  154320  173915  213234

per cogliere eventuali asimmetrie scorro riga per riga per vedere su quante di queste il prezzo medio appena trovato supera il prezzo mediano.

prop_mean_greater<-mean(realestate$mean_price>realestate$median_price)
round(prop_mean_greater*100,2)
## [1] 99.58

il risultato trovato ci dice che nel 99,58 percento dei casi (239 mesi su 240 osservati) il prezzo medio delle transazioni supera il prezzo mediano, dinamica del mercato immobiliare nota dove poche transazioni di valore alto(case di lusso,fondi commerciali ecc.) possono trascinare verso l’alto la media ovviamente senza intaccare la mediana, indicatore robusto agli outlier.

Efficacia degli annunci

Per determinare l’efficacia degli annunci le misure da utilizzare sono due: il totale delle vendite ‘sales’ e il numero di annunci attivi ‘listings’:

realestate$effectiveness <- realestate$sales/realestate$listings

summary(realestate$effectiveness)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
## 0.05014 0.08980 0.10963 0.11874 0.13492 0.38713

Visto che sarebbe approssimativo basare l’analisi su un valore univoco divido l’analisi per città:

realestate %>%
  group_by (city)%>%
  summarise(efficacia_media= mean(effectiveness),
            efficacia_sd= sd(effectiveness)) %>%
  arrange(desc(efficacia_media))
## # A tibble: 4 × 3
##   city                  efficacia_media efficacia_sd
##   <chr>                           <dbl>        <dbl>
## 1 Bryan-College Station          0.147        0.0729
## 2 Wichita Falls                  0.128        0.0247
## 3 Beaumont                       0.106        0.0267
## 4 Tyler                          0.0935       0.0235

Bryan-College Station risulta la città dove le campagne di marketing funzionano meglio seppur la deviazione standard dell’indice di efficacia sia 3 volte maggiore delle altre.

Le due features calcolate ci permettono rispettivamente di confermare: -la natura asimmetrica del mercato immobiliare, con outlier di valori(transazioni su case di lusso, complessi residenziali, edifici commerciali..) che trascinano le medie della transazioni verso l’alto in praticamente ogni mese osservato -in quali città le campagne di marketing vadano rafforzate per aumentare il tasso di vendita sulla base degli annunci pubblicati.

Step 7 — Analisi condizionata

Usando il pacchetto dplyr vado ad analizzare il dataset suddividendolo rispettivamente per città, anno e mese e per ciascuno di questi gruppi calcolo le statistiche di sintesi

Analisi per città

realestate %>%
  group_by(city) %>%
  summarise(
    media_vendite   = mean(sales),
    sd_vendite      = sd(sales),
    media_volume    = mean(volume),
    sd_volume       = sd(volume),
    media_prezzo    = mean(mean_price),
    sd_prezzo       = sd(mean_price),
    media_efficacia = mean(effectiveness),
    sd_efficacia    = sd(effectiveness),
    media_inventory = mean(months_inventory),
    sd_inventory    = sd(months_inventory)
  ) %>%
  kable(digits = 2, caption = "Statistiche di sintesi per città") %>%
  kable_styling(bootstrap_options = c("striped", "hover")) %>%
  scroll_box(width = "100%")
Statistiche di sintesi per città
city media_vendite sd_vendite media_volume sd_volume media_prezzo sd_prezzo media_efficacia sd_efficacia media_inventory sd_inventory
Beaumont 177.38 41.48 26.13 6.97 146640.4 11232.13 0.11 0.03 9.97 1.65
Bryan-College Station 205.97 84.98 38.19 17.25 183534.3 15149.35 0.15 0.07 7.66 2.25
Tyler 269.75 61.96 45.77 13.11 167676.8 12350.51 0.09 0.02 11.32 1.89
Wichita Falls 116.07 22.15 13.93 3.24 119430.0 11398.48 0.13 0.02 7.82 0.78

Analisi per anno

realestate %>%
  group_by(year) %>%
  summarise(
    media_vendite   = mean(sales),
    sd_vendite      = sd(sales),
    media_volume    = mean(volume),
    sd_volume       = sd(volume),
    media_prezzo    = mean(mean_price),
    sd_prezzo       = sd(mean_price),
    media_efficacia = mean(effectiveness),
    sd_efficacia    = sd(effectiveness),
    media_inventory = mean(months_inventory),
    sd_inventory    = sd(months_inventory)
  ) %>%
  kable(digits = 2, caption = "Statistiche di sintesi per anno") %>%
  kable_styling(bootstrap_options = c("striped", "hover")) %>%
  scroll_box(width = "100%")
Statistiche di sintesi per anno
year media_vendite sd_vendite media_volume sd_volume media_prezzo sd_prezzo media_efficacia sd_efficacia media_inventory sd_inventory
2010 168.67 60.54 25.68 10.80 150188.6 23279.55 0.10 0.03 9.97 2.08
2011 164.12 63.87 25.16 12.20 148250.6 24938.38 0.09 0.02 10.90 2.07
2012 186.15 70.91 29.27 14.52 150898.7 26438.50 0.11 0.03 9.88 1.61
2013 211.92 84.00 35.15 17.93 158705.2 26523.81 0.13 0.04 8.15 1.69
2014 230.60 95.51 39.77 21.19 163558.7 31740.53 0.16 0.06 7.06 1.75

Analisi per mese

realestate %>%
  group_by(month) %>%
  summarise(
    media_vendite   = mean(sales),
    sd_vendite      = sd(sales),
    media_volume    = mean(volume),
    sd_volume       = sd(volume),
    media_prezzo    = mean(mean_price),
    sd_prezzo       = sd(mean_price),
    media_efficacia = mean(effectiveness),
    sd_efficacia    = sd(effectiveness),
    media_inventory = mean(months_inventory),
    sd_inventory    = sd(months_inventory)
  ) %>%
  kable(digits = 2, caption = "Statistiche di sintesi per mese") %>%
  kable_styling(bootstrap_options = c("striped", "hover")) %>%
  scroll_box(width = "100%")
Statistiche di sintesi per mese
month media_vendite sd_vendite media_volume sd_volume media_prezzo sd_prezzo media_efficacia sd_efficacia media_inventory sd_inventory
1 127.40 43.38 19.00 8.37 145640.4 29819.11 0.08 0.02 8.84 1.97
2 140.85 51.07 21.65 10.09 148840.5 25120.42 0.09 0.02 9.06 1.98
3 189.45 59.18 29.38 12.02 151136.5 23237.92 0.12 0.03 9.40 2.06
4 211.70 65.40 33.30 14.52 151461.3 26174.30 0.13 0.04 9.72 2.24
5 238.85 83.12 39.70 19.02 158235.0 25787.19 0.14 0.05 9.68 2.38
6 243.55 95.00 41.30 21.08 161545.8 23470.46 0.14 0.06 9.70 2.41
7 235.75 96.27 39.12 21.41 156881.0 27220.12 0.14 0.07 9.62 2.50
8 231.45 79.23 38.01 18.05 156455.6 28253.21 0.14 0.05 9.39 2.45
9 182.35 72.52 29.60 15.22 156522.3 29669.41 0.11 0.03 9.19 2.52
10 179.90 74.95 29.08 15.13 155897.4 32527.29 0.11 0.04 8.94 2.44
11 156.85 55.47 24.81 11.15 154233.0 29684.87 0.10 0.03 8.66 2.37
12 169.40 60.75 27.09 12.57 154995.5 27008.87 0.12 0.04 8.12 2.27

Le tre suddivisioni servono rispettivamente ad analizzare eventuali differenze tra città con strutture economiche diverse (come già accennato nello step 6), i cambiamenti dovuti alla evoluzione temporale nel mercato immobiliare di anno in anno e i pattern stagionali dovuti ai movimenti di ogni mese nel corso degli anni. Tuttavia visto che suddividere per una sola variabile lascia una porzione delle informazioni nascosta procedo ad una analisi incrociata tra variabili per cogliere sfumature dei dati non visibili.

Analisi incrociata città × anno

realestate %>%
  group_by(city, year) %>%
  summarise(
    media_vendite   = mean(sales),
    media_volume    = mean(volume),
    media_prezzo    = mean(mean_price),
    media_efficacia = mean(effectiveness),
    media_inventory = mean(months_inventory),
    .groups = "drop"
  ) %>%
  kable(digits = 2, caption = "Statistiche di sintesi per città × anno") %>%
  kable_styling(bootstrap_options = c("striped", "hover")) %>%
  scroll_box(width = "100%", height = "400px")
Statistiche di sintesi per città × anno
city year media_vendite media_volume media_prezzo media_efficacia media_inventory
Beaumont 2010 156.17 22.65 146582.5 0.09 10.91
Beaumont 2011 144.00 21.10 145922.0 0.08 11.73
Beaumont 2012 171.92 24.47 141475.9 0.10 10.78
Beaumont 2013 201.17 30.31 150079.0 0.12 8.78
Beaumont 2014 213.67 32.13 149142.7 0.13 7.64
Bryan-College Station 2010 167.58 28.73 174601.8 0.11 8.67
Bryan-College Station 2011 167.42 28.93 173689.0 0.10 9.80
Bryan-College Station 2012 196.75 35.36 179360.6 0.12 8.94
Bryan-College Station 2013 237.83 45.12 187315.8 0.17 6.50
Bryan-College Station 2014 260.25 52.81 202704.3 0.24 4.38
Tyler 2010 227.50 36.35 159537.5 0.07 12.63
Tyler 2011 238.83 38.55 160248.0 0.08 13.47
Tyler 2012 263.50 44.01 165533.0 0.09 11.58
Tyler 2013 287.42 50.32 174501.8 0.10 10.18
Tyler 2014 331.50 59.60 178563.5 0.12 8.76
Wichita Falls 2010 123.42 14.97 120032.5 0.13 7.68
Wichita Falls 2011 106.25 12.05 113143.6 0.11 8.62
Wichita Falls 2012 112.42 13.23 117225.3 0.13 8.21
Wichita Falls 2013 121.25 14.85 122924.3 0.14 7.13
Wichita Falls 2014 117.00 14.54 123824.4 0.13 7.44

Visualizzazione grafica

Andamento del prezzo medio per città nel tempo

realestate %>%
  group_by(city, year) %>%
  summarise(media_prezzo = mean(mean_price),
            sd_prezzo = sd(mean_price),                      
            .groups = "drop") %>%
  ggplot(aes(x = year, y = media_prezzo, color = city, fill = city)) +   
  geom_ribbon(aes(ymin = media_prezzo - sd_prezzo,           
                  ymax = media_prezzo + sd_prezzo),
              alpha = 0.2, color = NA) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  labs(title = "Andamento del prezzo medio per città (2010-2014)",
       subtitle = "Linea = media annuale; bande di Bollinger = ± 1 sd",   
       x = "Anno",
       y = "Prezzo medio (USD)",
       color = "Città",
       fill = "Città") +                                      
  theme_minimal()

Dal grafico si nota come delle 4 città del dataset nel corso dei cinque anni analizzati solo in due il prezzo medio cresce nel corso degli anni,Tyler e Bryan-College Station. Beaumont e Wichita Falls a livello di prezzi restano pressochè stagnanti al netto di oscillazioni lievi nel corso del quinquennio.

Stagionalità delle vendite per città

realestate %>%
  group_by(city, month) %>%
  summarise(media_vendite = mean(sales), .groups = "drop") %>%
  ggplot(aes(x = month, y = media_vendite, color = city)) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  scale_x_continuous(breaks = 1:12) +
  labs(title = "Stagionalità delle vendite per città",
       x = "Mese",
       y = "Vendite medie",
       color = "Città") +
  theme_minimal()

Anche nella analisi di come variano le vendite nel corso dell’anno Tyler e Bryan-College Station mostrano dei trend molto più netti rispetto alle altre due città.Si può vedere chiaramente dal grafico come in entrambe la distribuzione delle vendite assuma una forma “gaussiana” toccando il picco nel mese di Giugno con valori molto piu bassi nei primi e negli ultimi mesi dell’anno. Lo stesso trend è a malapena visibile a Beaumont e Wichita Falls dove la “campana” delle vendite ha una durata più ampia ma molto meno netta.

Per completare l’analisi della stagionalità, verifico se anche i prezzi mostrano oscillazioni mensili:

realestate %>%
  group_by(city, month) %>%
  summarise(media_prezzo = mean(median_price), 
            sd_prezzo = sd(median_price),
            .groups = "drop") %>%
  ggplot(aes(x = month, y = media_prezzo, color = city, fill = city)) +
  geom_ribbon(aes(ymin = media_prezzo - sd_prezzo,
                  ymax = media_prezzo + sd_prezzo),
              alpha = 0.15, color = NA) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  scale_x_continuous(breaks = 1:12) +
  labs(title = "Stagionalità del prezzo mediano per città",
       subtitle = "Linea = media mensile su 2010-2014; fascia = ± 1 sd",
       x = "Mese",
       y = "Prezzo mediano medio (USD)",
       color = "Città",
       fill = "Città") +
  theme_minimal()

Il grafico mostra l’evoluzione dei prezzi medi nel corso dei mesi dell’anno, l’ampiezza del ‘ribbon’ attorno a ogni linea spiega quanto questi prezzi cambino nel corso degli anni analizzati.

In merito ai dati osservati tutte le città nel corso dei dodici mesi vedono una crescita del prezzo mediano, tuttavia con dinamiche eterogenee: la comune “campana estiva” dei volumi lascia spazio a tratti distintivi di ognuna. Tyler è l’unica con distribuzione dei prezzi a campana con picco nella stagione calda, Wichita ha un unico picco a Giugno,Beaumont ad agosto mentre Bryan-College Station ha un trend di crescita costante lungo l’anno solare.

Step 8 — Data visualization

Dopo aver analizzato i dati vado a mostrarne , grazie a ggplot2, i risultati più utili e esplicativi per i fruitori di questo report.

Boxplot del prezzo mediano per città

Il boxplot è la rappresentazione più utile per mostrare come si distribuisce una variabile quantitativa tra gruppi, ogni “scatola” ha una ampiezza data dalla differenza tra terzo e primo quartile, al suo interno si trova la mediana dei valori assunti dalla variabile per quel gruppo, i baffi si estendono solitamente per una misura convenzionale di 1,5 IQR (range interquartile) oltre gli estremi della box, i valori al di fuori vengono considerati outlier.

iqr_text <- realestate %>%
  group_by(city) %>%
  summarise(IQR_val = IQR(median_price)) %>%
  mutate(riga = paste0(city, ": IQR = ", IQR_val)) %>%
  pull(riga) %>%
  paste(collapse = "\n")

ggplot(realestate, aes(x = city, y = median_price, fill = city)) +
  geom_boxplot(alpha = 0.7) +
  annotate("label", x = 4.5, y = 180000, label = iqr_text,
           hjust = 1, vjust = 1, size = 3, fill = "white") +
  labs(title = "Distribuzione del prezzo mediano per città",
       x = "Città",
       y = "Prezzo mediano (USD)") +
  theme_minimal() +
  theme(legend.position = "none")

Dal grafico risulta ben chiaro che il mercato della città di Bryan- College Station abbia prezzi più alti , mentre il valore IQR più basso si traduce in una maggior concentrazione dei valori attorno al ‘median price’, viceversa Wichita Falls ha i prezzi mediamente più bassi ma un range interquartile ben più ampio delle altre città, coprendo prezzi sotto i 100 mila dollari che altrove non si trovano.

Boxplot del volume per città e per anno

ggplot(realestate, aes(x = city, y = volume, fill = factor(year))) +
  geom_boxplot(alpha = 0.7) +
  labs(title = "Distribuzione del volume per città e anno",
       x = "Città",
       y = "Volume (milioni USD)",
       fill = "Anno") +
  theme_minimal()

Da questo secondo grafico la cosa che balza subito all’occhio è che il mercato immobiliare nel corso del quinquennio è cresciuto ovunque tranne a Wichita, anzi dal 2010 al 2014 il valore mediano del volume di affari è persino diminuito, questo conferma che la città rappresenta una nicchia che esula dalle dinamiche di mercato classiche, cosa già appurata dal box precedente sul prezzo mediano.

Bar chart sovrapposto delle vendite per mese e città

I grafici a barre sovrapposte serviranno a mostrare le vendite per mese, suddivise per città. Avendo già la somma come valore da rappresentare sull’asse y uso ‘geom_col()’.

realestate %>%
  group_by(month, city) %>%
  summarise(vendite_totali = sum(sales), .groups = "drop") %>%
  ggplot(aes(x = factor(month), y = vendite_totali, fill = city)) +
  geom_col() +
  labs(title = "Vendite totali per mese e città",
       x = "Mese",
       y = "Vendite totali",
       fill = "Città") +
  theme_minimal()

Il grafico così fatto non restituisce informazioni importanti se non il solito mercato stantio di Wichita che tuttavia tende a notarsi più per la collocazione alla base delle barre. Per dare un senso maggiore al grafico aggiungo la versione normalizzata:

realestate %>%
  group_by(month, city) %>%
  summarise(vendite_totali = sum(sales), .groups = "drop") %>%
  group_by(month) %>%
  mutate(percentuale = vendite_totali / sum(vendite_totali)) %>%
  ungroup() %>%
  ggplot(aes(x = factor(month), y = percentuale, fill = city)) +
  geom_col() +
  geom_text(aes(label = scales::percent(percentuale, accuracy = 1)),
            position = position_stack(vjust = 0.5),
            size = 3, color = "white", fontface = "bold") +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Quota di vendite per città in ogni mese",
       x = "Mese",
       y = "Quota di vendite (%)",
       fill = "Città") +
  theme_minimal()

Con questa visualizzazione il grafico offre informazioni più utili: -la città di Tyler si conferma quella che traina il mercato a prescindere dal periodo dell’anno considerato, seppur con una lieve contrazione nel periodo estivo -Bryan-College risulta quella che gode maggiormente del periodo estivo per accrescere la propria fetta di mercato (dal 23% invernale al 33% di giugno) - le tre città restanti seppur con caratteristiche di mercato differenti pagano la contrazione estiva a favore di Bryan-College.

Per cogliere ulteriori informazioni legate al periodo storico può servire l’aggiunta della variabile ‘year’ per cogliere il cambiamento nel corso degli anni. Allo scopo di non appesantire il grafico utilizzo il faceting, andando a generare un sotto-grafico per ogni anno:

realestate %>%
  group_by(year, month, city) %>%
  summarise(vendite_totali = sum(sales), .groups = "drop") %>%
  ggplot(aes(x = factor(month), y = vendite_totali, fill = city)) +
  geom_col() +
  facet_wrap(~ year, nrow = 2, scales = "free_x") + 
  labs(
    title = "Vendite totali per mese e città, suddivise per anno",
    x = "Mese",
    y = "Vendite totali",
    fill = "Città"
  ) +
  theme_minimal() +
  theme(panel.spacing = unit(1, "lines"))

Il grafico ora permette di notare la crescita del mercato immobiliare complessivo. All’interno dell’anno persiste un calo complessivo nel mese di settembre. Per quanto riguarda le singole città è importante notare come il picco delle vendite si sia lentamente spostato dal secondo al terzo trimestre nel corso degli anni, frutto di una crescita costante delle vendite nel periodo estivo riscontrabile specialmente a Tyler e Bryan-College Station.

Line chart: andamento dei volumi nel tempo per città

Per cogliere la stagionalità nelle città del dataset costruisco una line chart analizzando il volume di vendita nel corso degli anni:

realestate %>%
  group_by(city, year, month) %>%
  summarise(volume_medio = mean(volume), .groups = "drop") %>%
  mutate(periodo = year + (month - 1)/12) %>%
  ggplot(aes(x = periodo, y = volume_medio, color = city)) +
  geom_line(linewidth = 0.8) +
  labs(title = "Evoluzione mensile del volume per città (2010-2014)",
       x = "Periodo (anno + frazione mese)",
       y = "Volume medio (milioni USD)",
       color = "Città") +
  theme_minimal()

A fronte di una trend-line crescente nel corso degli anni ad eccezione di Wichita, è facilmente visibile come i mercati di Tyler e Bryan abbiano una forte componente stagionale ripetendo nel corso del tempo gli stessi picchi nel corso del periodo estivo. Diversamente Beaumont e Wichita presentano volumi frastagliati e meno legati al periodo dell’anno.

Line chart: efficacia degli annunci nel corso dell’anno

La misura sull’efficacia degli annunci calcolata negli step precedenti può essere un buon indicatore per orientare le campagne di marketing nelle varie città:

realestate %>%
  group_by(month, city) %>%
  summarise(eff_media = mean(effectiveness), .groups = "drop") %>%
  ggplot(aes(x = factor(month), y = eff_media, color = city, group = city)) +
  annotate("rect", xmin = 4, xmax = 9, ymin = -Inf, ymax = Inf,
           alpha = 0.1, fill = "orange") +
  geom_line(size = 1.2) +
  geom_point(size = 2.5) +
  scale_color_brewer(palette = "Set1") + 
  labs(
    title = "Stagionalità dell'Efficacia degli Annunci (Effectiveness)",
    subtitle = "Media mensile storica (2010-2014) - Analisi per la pianificazione del marketing",
    x = "Mese",
    y = "Tasso di Conversione (Vendite / Annunci Attivi)",
    color = "Città"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", size = 14),
    panel.grid.minor = element_blank()
  )

Il grafico mostra un chiaro trend a campana nel corso dell’anno solare per tutte le città analizzate , dove nel periodo estivo si assiste e maggiori spostamenti di famiglie e studenti per vacanze e visite col favore del clima favorevole. Bryan tuttavia è quella che mostra il picco più netto nei mesi caldi dell’anno ,evidenziati nel grafico, raddoppiando la propria efficacia negli annunci pubblicati fino a oltre il 22%: più di un annuncio su 5 si traduce in una vendita! La presenza della Texas A&M University spiega perchè il mercato estivo sia così attivo rispetto al resto dell’anno,in concomitanza con l’inizio dell’anno accademico. Questo trend è quindi da considerare strutturale e non dovuto al caso, il che suggerisce di rafforzare gli investimenti in pubblicità in un periodo con un così alto tasso di conversioni
Delle restanti città vale la pena analizzare come la seconda in ordine di efficacia resti per tutti i periodi dell’anno mediamente Wichita Falls, che abbiamo visto avere logiche di mercato differenti dalle altre e prezzi molto più bassi, proprio questo fattore può essere alla base della “velocità di conversione” degli annunci in compravendite effettuate. A esclusione di Bryan la stagionalità dell’indice di efficacia ha un trend più “dolce” il che suggerisce una ripartizione delle risorse di marketing più uniforme nel corso dell’anno.

Step 9 — Conclusioni

L’analisi del dataset Texas Real Estate (240 osservazioni mensili, 4 città, 2010-2014) ha portato alla luce pattern strutturali di natura geografica , temporale e stagionale che vado a riepilogare:

Eterogeneità tra città. Le quattro città del dataset rappresentano quattro mercati strutturalmente diversi. Bryan-College Station e Tyler sono i mercati più dinamici, con prezzi medi elevati e un trend di crescita lungo il quinquennio. Beaumont e Wichita invece nel corso del quinquennio mantengono mediamente gli stessi prezzi attraversando anche fasi di contrazione, Wichita stessa rappresenta una nicchia di mercato a sè stante con prezzi molto più bassi rispetto alle altre. Questa eterogeneità giustifica statisticamente le analisi stratificate condotte negli Step 7 e 8: aggregare l’intero dataset nasconderebbe pattern molto diversi.

Trend temporale di crescita. Anche parlando di volumi BCT e Tyler guidano la classifica dei tassi di crescita, Beaumont ha un trend più morbido mentre Wichita conferma il suo ruolo di nicchia a parte rimanendo stagnante nel corso del quinquennio considerato.

Stagionalità sui volumi: marcata e uniforme. Nell’analisi dei volumi nel corso dell’anno tutte le città presentano la tipica crescita estiva del mercato immobiliare americano, tuttavia i grafici potrebbero sovrapporsi a coppie: BCT e Tyler con un picco unico e netto che copre i mesi caldi, Beaumont e Wichita con forme più frastagliate. Il tratto comune del dataset è un mercato invernale molto più moderato

Stagionalità sui prezzi: più debole e differenziata. A differenza dei volumi, la stagionalità sui prezzi mediani è più sfumata e segue pattern diversi per ogni città. Tyler mostra il pattern classico a campana estiva (+14% tra gennaio e giugno); Wichita Falls presenta un picco isolato a giugno (+25% rispetto a gennaio) seguito da un rapido ritorno ai valori di base; Bryan-College Station rimane più stabile con una lieve crescita autunnale; Beaumont non mostra pattern stagionali identificabili.

Efficacia degli annunci e ruolo dell’università. Bryan-College Station si distingue come la città con la maggiore efficacia commerciale, con un picco a luglio che supera il 22% di conversione da annunci a vendite. Questa anomalia è strutturale e non casuale: la presenza della Texas A&M University concentra la domanda nel periodo di inizio dell’anno accademico, generando una stagionalità più estrema e un’efficienza di mercato eccezionale ma confinata ai mesi estivi. Wichita Falls, pur essendo il mercato meno dinamico in termini di volumi, mostra una buona efficacia di conversione, attribuibile probabilmente ai prezzi più bassi che riducono i tempi di vendita.

Asimmetria interna ai prezzi. Il confronto tra prezzo medio (mean_price) e prezzo mediano (median_price) ha rivelato che nel 99.6% delle osservazioni la media supera la mediana. Questo conferma quantitativamente l’asimmetria positiva tipica del real estate: poche transazioni di valore elevato (immobili di lusso, fondi commerciali) trascinano la media verso l’alto, mentre la mediana resta più stabile. La mediana è quindi l’indicatore più robusto del “prezzo della casa tipica”.

In virtù di queste considerazioni le raccomandazioni operative sul mercato immobiliare texano devono seguire linee guida ben definite:

1. Differenziare le strategie per città. I quattro mercati non possono essere gestiti con un approccio uniforme. Bryan e Tyler richiedono un approccio di marketing aggressivo sugli immobili di valore medio-alto; Beaumont richiede strategie per consolidare la posizione intermedia e stabile di mercato; Wichita Falls va trattata come nicchia , con riguardo ad acquirenti di reddito più basso con offerte ad hoc.

2. Calibrare il marketing sulla stagionalità di Bryan-College Station. Il picco estivo di Bryan esige la convergenza delle risorse di marketing in quel periodo per massimizzare il ritorno economico, anche a discapito del periodo invernale che vede un mercato molto meno attivo.

3. Distribuire risorse uniformemente nelle altre città. A Beaumont, Tyler e Wichita Falls la stagionalità dei volumi è più dolce. Una distribuzione equilibrata delle risorse di marketing lungo l’anno è più efficiente di concentrazioni stagionali.

4. Sfruttare la stagionalità sui prezzi per immobili di valore. Wichita e Tyler , pur facendo parte di realtà differenti come visto hanno un mercato estivo con prezzo mediano più alto rispetto ai mesi invernali, il che offre la possibilità di mettere sul mercato fondi o case di valore maggiore che nel corso dell’anno hanno un mercato difficile.

5. Monitorare l’efficienza commerciale con KPI dedicati. L’indicatore effectiveness (sales/listings) costruito si è rivelato efficace alla comprensione delle dinamiche di vendita delle città del dataset. Pur rappresentando concettualmente l’inverso della variabile months_inventory le due se integrate possono fornire una lettura completa della velocità di rotazione dello stock.

6. Posizionamento sul segmento di prezzo dominante. L’analisi delle classi di median_price allo Step 4 ha mostrato che la fascia 135-160k dollari raccoglie il 40% delle osservazioni. Le inserzioni in questa fascia sono quindi quelle che incontrano una domanda maggiore pertanto dovrebbero essere il target principale.

Limiti dell’analisi

Il dataset non è esaustivo pertanto porta con sè diversi limiti:

  • copre solo 4 città, non l’intero Texas. Le generalizzazioni a livello statale richiederebbero dati più ampi.
  • Il periodo 2010-2014 coincide con la fase di ripresa post-crisi del mercato immobiliare USA: i trend osservati potrebbero essere contingenziali e non replicabili nel corso del tempo
  • gli immobili figurano senza ulteriori dettagli su metratura, vani e caratteristiche specifiche il chè nasconde una parte delle motivazioni dietro la variabilità dei prezzi osservati.
  • L’indicatore effectiveness assume implicitamente che sales e listings siano misurate nello stesso istante; eventuali disallineamenti temporali nei dati originari potrebbero introdurre distorsioni e invalidare parzialmente le considerazioni fatte.
  • I pattern di stagionalità sui prezzi (Step 7) sono basati su medie quinquennali per ogni mese, ma le fasce di deviazione standard mostrano variabilità tra anni anche significativa: eventuali outlier annuali potrebbero influenzare indicatori apparentemente robusti.

Queste limitazioni suggeriscono quindi di estendere l’analisi ampliando l’area geografica, il periodo considerato e valutando le caratteristiche specifiche degli immobili, così da rendere l’analisi più completa.