Il presente documento descrive l’analisi del mercato immobiliare del Texas richiesta da Texas Realty Insights. L’azienda desidera analizzare le tendenze del mercato immobiliare nello stato del Texas, sfruttando i dati storici relativi alle vendite di immobili. L’obiettivo è fornire insight statistici e visivi che supportino le decisioni strategiche di vendita e ottimizzazione delle inserzioni immobiliari.

Obiettivi del progetto:

Setup dataset

Il dataset viene importato e vengono aggiunte colonne che agevolano l’analisi temporale.

real_estate = read.csv("realestate_texas.csv", stringsAsFactors = TRUE)
summary_dataset = summary(real_estate)
# adding date column
real_estate$month_pad = sprintf("%02d", real_estate$month)
real_estate$date_str = paste(real_estate$year, real_estate$month_pad, sep = "-")
real_estate$date = as.Date(paste0(real_estate$date_str, "-01"))
# adding month name column
month_names = c("Gen", "Feb", "Mar", "Apr", "Mag", "Giu","Lug", "Ago", "Set",   "Ott", "Nov", "Dic")
real_estate$month_name = month_names[as.numeric(real_estate$month)]
real_estate$month_name = factor(real_estate$month_name, levels = month_names)
# adding quarter column
real_estate$quarter = paste0("Q", ceiling(real_estate$month / 3))

1. Analisi delle variabili

Il dataset è stato estratto dal file Real Estate Texas.csv

summary_dataset
##                     city         year          month           sales      
##  Beaumont             :60   Min.   :2010   Min.   : 1.00   Min.   : 79.0  
##  Bryan-College Station:60   1st Qu.:2011   1st Qu.: 3.75   1st Qu.:127.0  
##  Tyler                :60   Median :2012   Median : 6.50   Median :175.5  
##  Wichita Falls        :60   Mean   :2012   Mean   : 6.50   Mean   :192.3  
##                             3rd Qu.:2013   3rd Qu.: 9.25   3rd Qu.:247.0  
##                             Max.   :2014   Max.   :12.00   Max.   :423.0  
##      volume        median_price       listings    months_inventory
##  Min.   : 8.166   Min.   : 73800   Min.   : 743   Min.   : 3.400  
##  1st Qu.:17.660   1st Qu.:117300   1st Qu.:1026   1st Qu.: 7.800  
##  Median :27.062   Median :134500   Median :1618   Median : 8.950  
##  Mean   :31.005   Mean   :132665   Mean   :1738   Mean   : 9.193  
##  3rd Qu.:40.893   3rd Qu.:150050   3rd Qu.:2056   3rd Qu.:10.950  
##  Max.   :83.547   Max.   :180000   Max.   :3296   Max.   :14.900

Il dataset è composto da 8 variabili così definite:

table(real_estate$year)
## 
## 2010 2011 2012 2013 2014 
##   48   48   48   48   48
table(real_estate$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

Il dataset risulta bilanciato rispetto alle variabili qualitative nominali (city) e su scale ordinale (year e month), in quanto si registra lo stesso numero di osservazioni per città (60), mese (20) e anno (48). Questo permette di condurre analisi di confronto maggiormente affidabili e minimizza la difficoltà interpretativa.

Il range temporale è di 5 anni, dal 2010 al 2014. Tale ampiezza temporale permette di analizzare il trend storico delle vendite con una maggiore consistenza.

Per ogni città e anno il dataset offre 12 osservazioni corrispondenti al numero di mesi (1-12) in un anno. Questo aiuta ad analizzare un eventuale comportamento stagionale nelle vendite.

Infine, le variabili quantitative (sales, volume, median_price, listings, months_inventory) verranno studiate in relazione alle modalità assunte dalle variabili qualitative, tramite specifiche rappresentazioni grafiche.

2. Indici di posizione, variabilità e forma

Le variabili quantitative per le quali verranno calcolati gli indici di posizione, variabilità e forma sono: sales, volume, median_price, listings, months_inventory.

# Define a function to compute all the indexes
compute_indexes = function(x) {
  min_x     = min(x, na.rm=TRUE)
  q1        = quantile(x, 0.25, na.rm=TRUE)
  median_x  = median(x, na.rm=TRUE)
  mean_x    = mean(x, na.rm=TRUE)
  q3        = quantile(x, 0.75, na.rm=TRUE)
  max_x     = max(x, na.rm=TRUE)
  iqr_x     = IQR(x, na.rm=TRUE)
  var_x     = var(x, na.rm=TRUE)
  sd_x      = sd(x, na.rm=TRUE)
  cv_x      = sd_x / mean_x
  skew_x    = skewness(x)
  kurt_x    = kurtosis(x)
  c(
    Min       = min_x,
    '1st Qu.' = q1,
    Median    = median_x,
    Mean      = mean_x,
    '3rd Qu.' = q3,
    Max       = max_x,
    IQR       = iqr_x,
    Variance  = var_x,
    Std       = sd_x,
    CV        = cv_x,
    Skewness  = skew_x,
    Kurtosis  = kurt_x
  )
}
df_quantitative = real_estate[, c("sales","volume","median_price","listings","months_inventory")]
# Apply the function to the columns and transform the output into a table
summary_indexes = as.data.frame(
  t(round(sapply(df_quantitative, compute_indexes), 2))
)
summary_indexes = tibble::rownames_to_column(summary_indexes, "Variable")
summary_indexes
##           Variable      Min 1st Qu..25%    Median      Mean 3rd Qu..75%
## 1            sales    79.00      127.00    175.50    192.29      247.00
## 2           volume     8.17       17.66     27.06     31.01       40.89
## 3     median_price 73800.00   117300.00 134500.00 132665.42   150050.00
## 4         listings   743.00     1026.50   1618.50   1738.02     2056.00
## 5 months_inventory     3.40        7.80      8.95      9.19       10.95
##         Max      IQR    Variance      Std   CV Skewness Kurtosis
## 1    423.00   120.00 6.34430e+03    79.65 0.41     0.72     2.69
## 2     83.55    23.23 2.77270e+02    16.65 0.54     0.88     3.18
## 3 180000.00 32750.00 5.13573e+08 22662.15 0.17    -0.36     2.38
## 4   3296.00  1029.50 5.66569e+05   752.71 0.43     0.65     2.21
## 5     14.90     3.15 5.31000e+00     2.30 0.25     0.04     2.83

3. Identificazione delle variabili con maggiore variabilità e asimmetria

3.1 Variabilità

Per studiare la variaiblità di variabili aventi scala e unità di misura diverse, occorre analizzare il Coefficiente di Variazione (CV), ovvero l’indice di variabilità relativa calcolato al punto 2.

La variabile quantitativa volume risulta avere il CV maggiore con il 54%.

3.2 Asimmetria

Per studiare l’asimmetria delle variabili di un dataset, occorre analizzare l’indice di Fisher (Skewness), ovvero l’indice di asimmetria calcolato al punto 2. In questo caso verrà analizzato il valore assoluto di tale indice, per determinare la variabile con la maggiore asimmetria.

Anche in questo caso, la variabile quantitativa volume risulta avere una distribuzione maggiormente asimmetrica con il valore assoluto di 0.88. Nel caso specifico della variabile volume, il valore risulta positivo anche senza applicare il valore assoluto, quindi tale distribuzione è asimmetrica positiva.

4. Creazione di classi per una variabile quantitativa

Per la variabile quantitativa sales verrà studiata la distribuzione di frequenza in classi e verrà calcolato l’indice di eterogeneità di Gini.

4.1 Distribuzione di Frequenze - Sales

# freq distribution
N = dim(real_estate)[1]
real_estate$sales_cl = cut(real_estate$sales, 
                        breaks = seq(50,450,50))

ni = table(real_estate$sales_cl)
fi = table(real_estate$sales_cl)/N
Ni = cumsum(ni)
Fi = cumsum(fi)

distr_freq_sales = as.data.frame(cbind(ni, fi, Ni, Fi))

classes = unique(real_estate$sales_cl)
ggplot(distr_freq_sales, aes(x = classes, y = fi*100)) +
  geom_col(fill = "orange") +
  labs(title = "Distribuzione delle vendite (%)",
       x = "Classe di vendita",
       y = "Percentuale vendite (%)") +
  theme_minimal()

Osservando il grafico sulle distribuzioni di frequenze della variabile quantitativa sales, si conferma una distribuzione asimmetrica positiva (Skewness=0.72), dove la Media è maggiore della Mediana. Il picco è centrato intorno alla classe (100-150], con una cosa verso valori via via crescenti.

4.2 Indice di eterogeneità (Gini)

# Function to compute the gini index
gini_index = function(x){
  ni=table(x)
  fi=ni/length(x)
  fi2=fi^2
  J=length(ni)
  
  gini = 1-sum(fi2)
  gini.normalizzato = gini / ((J-1)/J)
  
  return(gini.normalizzato)
}
gini_sales = round(gini_index(real_estate$sales) * 100,2)
gini_sales
## [1] 99.84

Lo studio dell’indice di Gini per la variabile sales conduce al valore di 99.84%, ovvero il numero di vendite è fortemente eterogeneo, ovvero non è equamente distribuito tra città e periodi (mese e anno).

5. Calcolo della probabilità

# Prob: city is "Beaumont"
n_beaumont = sum(real_estate$city == "Beaumont")
prob_beaumont = n_beaumont / N
# Prob: month is "Luglio"
n_luglio = sum(real_estate$month == 7)
prob_luglio = n_luglio / N
# Prob: month is "Dicembre" AND year is "2012" 
n_dic_2012 = sum(real_estate$month == 7 & real_estate$year == 2012)
prob_dic_2012 = n_dic_2012 / N

La probabilità di trovare un record del dataset associato alla città Beaumont è del 25%. La probabilità di trovare un record del dataset associato al mese di Luglio è di circa 8.3%. La probabilità di trovare un record del dataset associato al periodo di Dicembre 2012 è di circa 1.7%.

6. Creazione di nuove variabili

Verranno create e aggiunte al dataset due nuove variabili:

Il prezzo medio di vendita di immobili è dato dal rapporto tra il volume totale di immobili venduti (in dollari) e la quantità di immobili venduti nello stesso periodo (mese e anno) e città.

# summary new variables
real_estate$mean_price = real_estate$volume / real_estate$sales * 10^6
round(sum(real_estate$median_price < real_estate$mean_price) / N * 100, 1)
## [1] 99.6

Nel 99.6% dei casi il prezzo medio di vendita è superiore alla mediana, confermando una asimmetrica positiva della variabile sales.

L’efficacia degli annunci di vendita può essere calcolata come il rapporto tra la quantità di annunci attivi listings in un certo periodo (mese e anno) e città e la quantità di tempo necessaria per vendere tutte le inserzioni months_invetory, ovvero misurando la rapidità con cui gli annunci vengono venduti in uno stesso mese.

real_estate$listings_efficiency = real_estate$listings / real_estate$months_inventory

Le due nuove variabili presentano i seguenti indici statistici di posizione, variabilità e forma:

df_new_variables = real_estate[, c("mean_price","listings_efficiency")]
summary_indexes_nvar = as.data.frame(
  t(round(sapply(df_new_variables, compute_indexes), 2))
)
summary_indexes_nvar = tibble::rownames_to_column(summary_indexes_nvar, "Variable")
summary_indexes_nvar
##              Variable      Min 1st Qu..25%    Median      Mean 3rd Qu..75%
## 1          mean_price 97010.20   132938.94 156588.48 154320.37   173915.15
## 2 listings_efficiency   106.84      140.12    176.05    187.34      235.96
##         Max      IQR     Variance      Std   CV Skewness Kurtosis
## 1 213233.94 40976.22 736984385.27 27147.46 0.18    -0.07     2.22
## 2    329.28    95.83      3343.95    57.83 0.31     0.37     2.12

Entrambe le variabili sono lievemente asimmetriche e leptocurtiche, con una variabilità compresa tra il 20-30% circa. In particolare, mean_price è asimmetrica positiva con una variabilità del 18%, mentre sales_efficiency è asimmetrica negativa con una variabilità del 31%.

E’ facilmente intuibile che tale variabile sia influenzata da condizioni esterne, come la città o la stagionalità. Per tale motivo verrà eseguita un’analisi condizionata per le città e nel tempo per verificare questa ipotesi.

7. Analisi condizionata

In questa sezione verrà condotta un’analisi statistica condizionata sull’efficacia degli annunci di vendita per città mese e anno.

cond_analysis = real_estate %>%
  group_by(city) %>%
  summarise(mean.price = mean(mean_price),
            std.price = sd(mean_price),
            mean.listings_efficiency = round(mean(listings_efficiency), 2),
            std.listings_efficiency = round(sd(listings_efficiency), 2))
cond_analysis
## # A tibble: 4 × 5
##   city        mean.price std.price mean.listings_effici…¹ std.listings_efficie…²
##   <fct>            <dbl>     <dbl>                  <dbl>                  <dbl>
## 1 Beaumont       146640.    11232.                   172.                  23.1 
## 2 Bryan-Coll…    183534.    15149.                   200.                  33.7 
## 3 Tyler          167677.    12351.                   261.                  29.3 
## 4 Wichita Fa…    119430.    11398.                   117.                   6.33
## # ℹ abbreviated names: ¹​mean.listings_efficiency, ²​std.listings_efficiency
# efficiency by city
efficiency_cl = cut(real_estate$listings_efficiency,
                   breaks = seq(100,400,50))

ggplot(data=real_estate)+
  geom_bar(
    aes(x=efficiency_cl,fill=city),
    position = "fill",
    stat = "count"
  )+
  labs(title="Distribuzione dell'efficacia degli annunci per City",
       x="Classi di Efficacia",
       y="Frequenze relative")+
  theme_minimal()+
  theme(legend.position = "bottom")

# efficiency over time
ggplot(real_estate, aes(x = date, y = listings_efficiency, color = city, group = city)) +
  geom_line(linewidth = 0.5) +
  labs(title = "Efficacia annunci per città nel tempo",
       x = "Tempo",
       y = "Efficacia",
       color = "Città") +
  scale_x_date(date_labels = "%b %Y", date_breaks = "3 months") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = 'bottom')

# scatter listings - listings_efficiency
ggplot(data = real_estate, aes(x = listings, y = listings_efficiency, color = city, group = city)) +
  geom_point(size = 3, alpha = 0.7) +
  labs(title = "Annunci attivi per Efficacia degli annunci",
       x = "Listings",
       y = "Efficacia",
       color = "Città") +
  theme_minimal()+
  theme(legend.position = "bottom")

# scatter months_inventory - listings_efficiency
ggplot(data = real_estate, aes(x = months_inventory, y = listings_efficiency, color = city, group = city)) +
  geom_point(size = 3, alpha = 0.7) +
  labs(title = "Tempi di vendita per Efficacia degli annunci",
       x = "Months inventory",
       y = "Efficacia",
       color = "Città") +
  theme_minimal()+
  theme(legend.position = "bottom")

# scatter listings - months_inventory
ggplot(data = real_estate, aes(x = listings, y = months_inventory, color = city, group = city)) +
  geom_point(size = 3, alpha = 0.7) +
  labs(title = "Annunci attivi per Tempi di vendita",
       x = "Listings",
       y = "Months inventory",
       color = "Città") +
  theme_minimal()+
  theme(legend.position = "bottom")

Come si evince dai grafici, nella città di Tyler si ha la massima efficacia di annunci di vendita, mentre la città di Wichita Falls la minima. Inoltre, tutte le città presentano un trend crescente nei 5 anni analizzati, eccetto la città di Wichita Falls, la quale è in leggero calo.

Osservando la quantità di annunci attivi e la quantità di tempo necessaria a venderli, si osserva un trend decresente di entrambe le variabili, ovvero sia un calo nell’attivazione di nuovi annunci, ma anche una riduzione del tempo necessario a venderli. In particolare, la quantità di annunci attivi descresce più lentamente del tempo di vendita, segno della crescente performance di vendita.

La città di Wichita Falls non segue precisamente questo andamento, il tempo di vendita si mantiene pressohé costante, segno diuna difficoltà nel concretizzare le vendite.

8. Creazione di visualizzazioni con ggplot2

# boxplot median_price per city
ggplot(real_estate, aes(x = city, y = median_price, fill = factor(year))) +
  geom_boxplot() +
  labs(
    x = "",
    y = "Price (dollari)",
    fill = "",
    title = "Distribuzione dei prezzi mediani per città"
  ) +
  theme_minimal() + 
  theme(legend.position = "bottom")

## sales by season
# monthly
sales_by_month = real_estate %>%
  group_by(month_name, city, year) %>%
  summarise(mean_sales = mean(sales, na.rm = TRUE), .groups = "keep")

ggplot(sales_by_month, aes(x = month_name, y = mean_sales, fill = city)) +
  geom_bar(stat = "identity") +
  labs(title = "Vendite medie per Mese e Città",
       x = "",
       y = "Vendite") +
  theme_minimal()+
  theme(legend.position = "bottom")

# quarterly
sales_by_quarter = real_estate %>%
  group_by(quarter, city, year) %>%
  summarise(mean_sales = mean(sales, na.rm = TRUE), .groups = "keep")

ggplot(sales_by_quarter, aes(x = quarter, y = mean_sales, fill = city)) +
  geom_bar(stat = "identity") +
  facet_wrap(~year, ncol = 5) +
  labs(title = "Vendite medie per Trimestre e Città",
       x = "",
       y = "Vendite") +
  theme_minimal()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = "bottom")

# sales over time
ggplot(real_estate, aes(x = date, y = sales, color = city, group = city)) +
  geom_line(linewidth = 0.5) +
  labs(title = "Vendite per città nel tempo",
       x = "",
       y = "Vendite",
       color = "") +
  scale_x_date(date_labels = "%b %Y", date_breaks = "3 months") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = 'bottom')

# sales over time 2
sales_month_year = real_estate %>%
  group_by(month_name, year) %>%
  summarise(mean_sales = mean(sales, na.rm = TRUE), .groups = "keep")

ggplot(sales_month_year, aes(x = month_name, y = mean_sales, color = as.factor(year), group = year)) +
  geom_line(linewidth = 0.5) +
  geom_point() +
  labs(title = "Vendite medie per Anno",
       x = "",
       y = "Vendite",
       color = "") +
  theme_minimal()+
  theme(legend.position = "bottom")

Osservando il primo grafico, i prezzi mediani oscillano tra 125.000$ e 150.000$ circa. Inoltre, si osserva una graduale crescita nel periodo temporale in esame. Infine, per ogni città la variabilità si mantiene abbastanza costante e con pochi outlier, segno di un mercato stabile.

La città Bryan-College Station risulta avere i prezzi di vendita più elevati, con una impennata nell’anno 2014. seguita dalle città di Tyler e Beaumont. Anche in questo caso la città Wichita Falls è in fondo alla classifica con il prezzo mediano inferiore.

Il secondo e terzo grafico intendono dimostrare l’andamento stagionale delle vendite. In particolare, nel secondo grafico le vendite vengono raggruppate per mese verificando mediamente in quale periodo dell’anno si registrano performance migliori; il terzo grafico, invece, fornisce un maggiore dettaglio, raggruppando i dati per trimestre per ogni singolo anno. Si osserva che il periodo centrale dell’anno (Q2 e Q3) registra il più alto numero di vendite, ed è in crescita costante.

Infine, dagli ultimi due grafici si può constatare che Il 2014 è stato l’anno che ha registrato il maggior numero di vendite. Nuovamente, la città Tyler si conferma la zona con il più alto numero di vendite, anche se la città Bryan-College Station sta raggiungendo le stesse performance di Tyler nei momenti di picco. Infine, la città Wichita Falls con la più bassa efficacia di annunci di vendita effettivamente riflette un trend di vendite non in crescita.

9. Conclusioni

E’ stato analizzato il mercato immobiliare dello stato del Texas considerando il campione composta da 4 città in un periodo di tempo compreso tra il 2010 e il 2014. Le vendite sembrano avere un trend crescente nel tempo con una modalità stagionale, toccando il picco massimo di vendita nel secondo trimestre (Apr-Giu), ed un massimo generale nel 2014.

La variabile chiave sembra essere l’efficacia degli annunci di vendita, definita come il rapporto tra il numero di annunci attivi ed il tempo necessario a venderli. Tale metrica divide il dataset in tre cluster: il primo composto dalla sola città di Tyler, in cui si registra la più elevata efficacia di annunci di vendita, il più alto numero di vendite in assoluto e il secondo prezzo mediano di vendita di immobili.

Il secondo cluster è composto dalle città di Bryan-College Station e Beaumont, nelle quali si registra la stessa efficacia degli annunci, nonostante il prezzo mediano di vendita sia decisamente diverso (Bryan-College Station è la città più cara in assoluto).

Il terzo cluster è composto dalla sola città di Wichta Falls, la quale risulta la più economina e la più stabile come numero di vendite. In questo caso, l’efficacia degli annunci di vendita è molto bassa e si mantiene più o meno costante, e la causa sembra essere che il numero di annunci attivi e il tempo necessario per venderli seguono lo stesso andamento, ovvero descresono con la stessa velocità, a differenza delle altre città in cui il tempo di vendita si riduce più rapidamente della quantità di annunci che si attivano. Quindi la soluzione non sembra essere aumentare gli annunci, ma piuttosto occorre approfondire gli aspetti legati ai tempi di vendita e al tipo di immobili che si cerca di vendere.