Step 1: Analisi delle Variabili

Il dataset contiene le seguenti variabili:


Step 2: Indici di Posizione, Variabilità e Forma

Le variabili su cui ha senso calcolare gli indici numerici sono: sales, volume, median_price, listings e months_inventory. Per city, year e month vengono create distribuzioni di frequenza.

indici_stat = function(x, nome_var){
  cat("Variabile:", nome_var, "\n")
  cat("Minimo:", min(x, na.rm = T), "\n")
  cat("Q1:", quantile(x, 0.25, na.rm = T), "\n")
  cat("Mediana:", median(x, na.rm = T), "\n")
  cat("Media:", mean(x, na.rm = T), "\n")
  cat("Q3:", quantile(x, 0.75, na.rm = T), "\n")
  cat("Massimo:", max(x, na.rm = T), "\n")
  cat("Range:", max(x, na.rm = T)-min(x, na.rm = T), "\n")
  cat("IQR:", IQR(x, na.rm = T), "\n")
  cat("Dev. Standard:", sd(x, na.rm = T), "\n")
  cat("Coeff. Variazione:", sd(x, na.rm = T)/mean(x, na.rm = T)*100, "\n")
  cat("Assimmetria:", skewness(x, na.rm = T), "\n")
  cat("Curtosi:", kurtosis(x, na.rm = T)-3, "\n")
}
indici_stat(sales,           "Sales (Vendite)")
## Variabile: Sales (Vendite) 
## Minimo: 79 
## Q1: 127 
## Mediana: 175.5 
## Media: 192.2917 
## Q3: 247 
## Massimo: 423 
## Range: 344 
## IQR: 120 
## Dev. Standard: 79.65111 
## Coeff. Variazione: 41.42203 
## Assimmetria: 0.718104 
## Curtosi: -0.3131764
indici_stat(volume,          "Volume (milioni $)")
## Variabile: Volume (milioni $) 
## Minimo: 8.166 
## Q1: 17.6595 
## Mediana: 27.0625 
## Media: 31.00519 
## Q3: 40.893 
## Massimo: 83.547 
## Range: 75.381 
## IQR: 23.2335 
## Dev. Standard: 16.65145 
## Coeff. Variazione: 53.70536 
## Assimmetria: 0.884742 
## Curtosi: 0.176987
indici_stat(median_price,    "Median price ($)")
## Variabile: Median price ($) 
## Minimo: 73800 
## Q1: 117300 
## Mediana: 134500 
## Media: 132665.4 
## Q3: 150050 
## Massimo: 180000 
## Range: 106200 
## IQR: 32750 
## Dev. Standard: 22662.15 
## Coeff. Variazione: 17.08218 
## Assimmetria: -0.3645529 
## Curtosi: -0.6229618
indici_stat(listings,        "Listings (Annunci)")
## Variabile: Listings (Annunci) 
## Minimo: 743 
## Q1: 1026.5 
## Mediana: 1618.5 
## Media: 1738.021 
## Q3: 2056 
## Massimo: 3296 
## Range: 2553 
## IQR: 1029.5 
## Dev. Standard: 752.7078 
## Coeff. Variazione: 43.30833 
## Assimmetria: 0.6494982 
## Curtosi: -0.79179
indici_stat(months_inventory,"Months inventory")
## Variabile: Months inventory 
## Minimo: 3.4 
## Q1: 7.8 
## Mediana: 8.95 
## Media: 9.1925 
## Q3: 10.95 
## Massimo: 14.9 
## Range: 11.5 
## IQR: 3.15 
## Dev. Standard: 2.303669 
## Coeff. Variazione: 25.06031 
## Assimmetria: 0.04097527 
## Curtosi: -0.1744475

Distribuzioni di Frequenza

freq_city  = table(city);  print(freq_city);  prop.table(freq_city)
## city
##              Beaumont Bryan-College Station                 Tyler 
##                    60                    60                    60 
##         Wichita Falls 
##                    60
## city
##              Beaumont Bryan-College Station                 Tyler 
##                  0.25                  0.25                  0.25 
##         Wichita Falls 
##                  0.25
freq_year  = table(year);  print(freq_year)
## year
## 2010 2011 2012 2013 2014 
##   48   48   48   48   48
freq_month = table(month); print(freq_month)
## 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

Commento: Il volume è la variabile con la variabilità più alta (CV ≈ 54%) e la distribuzione più asimmetrica (skewness ≈ 0.88), mentre il prezzo mediano è la variabile più stabile (CV ≈ 17%) e l’unica con asimmetria negativa (skewness ≈ -0.36). Il months inventory è la variabile più simmetrica in assoluto: skewness quasi zero e curtosi leggermente negativa indicano una distribuzione molto vicina alla normale.


Step 3: Variabile con Maggiore Variabilità e Asimmetria

Variabile con la più alta variabilità: Volume

Il confronto va fatto tramite il coefficiente di variazione (CV), poiché le variabili hanno unità di misura diverse. Il CV normalizza la dispersione sulla media, rendendola adimensionale e comparabile. Il volume presenta il CV più alto (≈ 53.7%), accumulando la variabilità sia del numero di vendite che dei prezzi.

Variabile con la distribuzione più asimmetrica: Volume

Confrontando i valori di skewness in valore assoluto, il volume risulta il più asimmetrico (skewness ≈ 0.885), con coda a destra: poche osservazioni con volumi eccezionalmente alti trascinano la media (31M\() al di sopra della mediana (27M\)). All’opposto, months_inventory è praticamente simmetrico (skewness ≈ 0.04), mentre median_price è l’unica variabile con asimmetria negativa (-0.365).


Step 4: Creazione di Classi per una Variabile Quantitativa

Viene classificata la variabile median_price in 5 classi di ampiezza uguale.

data$price_class = cut(median_price, breaks = 5, include.lowest = T, right = T)

freq_price = table(data$price_class)
rel_freq   = prop.table(freq_price)
print(freq_price)
## 
##  [7.37e+04,9.5e+04]  (9.5e+04,1.16e+05] (1.16e+05,1.38e+05] (1.38e+05,1.59e+05] 
##                  18                  40                  73                  84 
##  (1.59e+05,1.8e+05] 
##                  25
print(round(rel_freq, 4))
## 
##  [7.37e+04,9.5e+04]  (9.5e+04,1.16e+05] (1.16e+05,1.38e+05] (1.38e+05,1.59e+05] 
##              0.0750              0.1667              0.3042              0.3500 
##  (1.59e+05,1.8e+05] 
##              0.1042
barplot(freq_price,
        main  = "Distribuzione del prezzo mediano per classi",
        xlab  = "Classi di prezzo ($)",
        ylab  = "Frequenza assoluta",
        col   = "lightblue",
        las   = 1,
        cex.names = 0.60)

gini.index = function(x){
  ni  = table(x)
  fi  = ni / length(x)
  fi2 = fi^2
  J   = length(table(x))
  gini             = 1 - sum(fi2)
  gini.normalizzato = gini / ((J-1)/J)
  return(gini.normalizzato)
}

gini.index(data$price_class)
## [1] 0.9259115

Commento: Un indice di Gini normalizzato vicino a 1 indica alta eterogeneità: le osservazioni sono distribuite in modo abbastanza uniforme tra le classi di prezzo, senza una concentrazione marcata in una singola fascia.


Step 5: Calcolo della Probabilità

Le probabilità sono calcolate in senso frequentista: p = casi favorevoli / casi totali.

n_tot = nrow(data)

# Probabilità Beaumont
n_beaumont   = sum(city == "Beaumont")
p_beaumont   = (n_beaumont / n_tot) * 100
cat("P(Beaumont):", p_beaumont, "%\n")
## P(Beaumont): 25 %
# Probabilità Luglio
n_luglio = sum(month == 7)
p_luglio = (n_luglio / n_tot) * 100
cat("P(Luglio):", p_luglio, "%\n")
## P(Luglio): 8.333333 %
# Probabilità Dicembre 2012
n_dicembre2012 = sum(month == 12 & year == 2012)
p_dicembre2012 = (n_dicembre2012 / n_tot) * 100
cat("P(Dicembre 2012):", p_dicembre2012, "%\n")
## P(Dicembre 2012): 1.666667 %

Commento: La probabilità di estrarre una riga relativa a Beaumont è pari al 25%, coerente con un dataset bilanciato su 4 città. La probabilità per il mese di luglio è circa 8.3% (1/12), e quella per dicembre 2012 circa 1.7%, corrispondente a un singolo mese di un singolo anno su tutti quelli disponibili.


Step 6: Creazione di Nuove Variabili

# Prezzo medio: volume totale (convertito in $) diviso numero di vendite
data$mean_price = (volume * 1000000) / sales
summary(data$mean_price)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   97010  132939  156588  154320  173915  213234
# Efficacia annunci: percentuale di annunci convertiti in vendita
data$efficacy = (sales / listings) * 100
summary(data$efficacy)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   5.014   8.980  10.963  11.874  13.492  38.713

Commento: Il prezzo medio (mean_price) è influenzato dalle transazioni di lusso e risulta generalmente superiore al prezzo mediano, confermando la distribuzione asimmetrica dei prezzi. L’efficacia degli annunci (efficacy) misura la capacità del mercato di convertire le inserzioni in vendite: valori più alti indicano mercati più dinamici con domanda sostenuta.


Step 7: Analisi Condizionata

Per Città

summary_city = data %>%
  group_by(city) %>%
  summarise(
    n_obs          = n(),
    media_sales    = mean(sales,        na.rm = T),
    sd_sales       = sd(sales,          na.rm = T),
    media_volume   = mean(volume,       na.rm = T),
    sd_volume      = sd(volume,         na.rm = T),
    media_prezzo   = mean(median_price, na.rm = T),
    sd_prezzo      = sd(median_price,   na.rm = T),
    media_efficacy = mean(efficacy,     na.rm = T)
  ) %>%
  arrange(desc(media_sales))

print(summary_city)
## # A tibble: 4 × 9
##   city  n_obs media_sales sd_sales media_volume sd_volume media_prezzo sd_prezzo
##   <chr> <int>       <dbl>    <dbl>        <dbl>     <dbl>        <dbl>     <dbl>
## 1 Tyler    60        270.     62.0         45.8     13.1       141442.     9337.
## 2 Brya…    60        206.     85.0         38.2     17.2       157488.     8852.
## 3 Beau…    60        177.     41.5         26.1      6.97      129988.    10105.
## 4 Wich…    60        116.     22.2         13.9      3.24      101743.    11320.
## # ℹ 1 more variable: media_efficacy <dbl>
ggplot(summary_city, aes(x = factor(city), y = media_prezzo)) +
  geom_col(fill = "lightblue") +
  geom_errorbar(aes(ymin = media_prezzo - sd_prezzo,
                    ymax = media_prezzo + sd_prezzo), width = 0.3, color = "red") +
  labs(title = "Media del Prezzo per Città (con Dev. Standard)",
       x = "Città", y = "Media Prezzo ($)")

Per Anno

summary_year = data %>%
  group_by(year) %>%
  summarise(
    totale_sales  = sum(sales,         na.rm = T),
    totale_volume = sum(volume,        na.rm = T),
    media_prezzo  = mean(median_price, na.rm = T)
  )

print(summary_year)
## # A tibble: 5 × 4
##    year totale_sales totale_volume media_prezzo
##   <int>        <int>         <dbl>        <dbl>
## 1  2010         8096         1232.      130192.
## 2  2011         7878         1208.      127854.
## 3  2012         8935         1405.      130077.
## 4  2013        10172         1687.      135723.
## 5  2014        11069         1909.      139481.

Per Mese

summary_month = data %>%
  group_by(month) %>%
  summarise(
    media_sales  = mean(sales,  na.rm = T),
    sd_sales     = sd(sales,    na.rm = T),
    media_volume = mean(volume, na.rm = T),
    sd_volume    = sd(volume,   na.rm = T)
  ) %>%
  arrange(month)

print(summary_month)
## # A tibble: 12 × 5
##    month media_sales sd_sales media_volume sd_volume
##    <int>       <dbl>    <dbl>        <dbl>     <dbl>
##  1     1        127.     43.4         19.0      8.37
##  2     2        141.     51.1         21.7     10.1 
##  3     3        189.     59.2         29.4     12.0 
##  4     4        212.     65.4         33.3     14.5 
##  5     5        239.     83.1         39.7     19.0 
##  6     6        244.     95.0         41.3     21.1 
##  7     7        236.     96.3         39.1     21.4 
##  8     8        231.     79.2         38.0     18.0 
##  9     9        182.     72.5         29.6     15.2 
## 10    10        180.     75.0         29.1     15.1 
## 11    11        157.     55.5         24.8     11.2 
## 12    12        169.     60.7         27.1     12.6
ggplot(summary_month, aes(x = factor(month), y = media_sales)) +
  geom_col(fill = "blue") +
  geom_errorbar(aes(ymin = media_sales - sd_sales,
                    ymax = media_sales + sd_sales), width = 0.3, color = "red") +
  labs(title = "Media delle Vendite per Mese (con Dev. Standard)",
       x = "Mese", y = "Media Vendite")

Commento: Le vendite mostrano una chiara stagionalità con picchi nei mesi primaverili ed estivi. Le città presentano prezzi medi e variabilità molto differenti tra loro, confermando l’eterogeneità del mercato texano.


Step 8: Visualizzazioni con ggplot2

Boxplot: Prezzo Mediano per Città

ggplot(data, aes(x = reorder(city, median_price, median), y = median_price, fill = city)) +
  geom_boxplot(outlier.colour = "red", outlier.shape = 1, alpha = 0.5) +
  labs(title = "Distribuzione del Prezzo Mediano per Città",
       x = "Città", y = "Prezzo Mediano ($)")

Commento: Il boxplot mostra mediana, IQR e outlier per ogni città. Le città con il box posizionato più in alto hanno prezzi mediani strutturalmente più elevati. L’ampiezza del box indica la variabilità interna: box ampi corrispondono a mercati più eterogenei. I punti rossi (outlier) verso l’alto segnalano transazioni occasionali di valore eccezionale.

Boxplot: Volume delle Vendite per Città e per Anno

ggplot(data, aes(x = reorder(city, volume, median), y = volume, fill = city)) +
  geom_boxplot(alpha = 0.7) +
  labs(title = "Distribuzione del Volume delle Vendite per Città",
       x = "Città", y = "Volume (milioni $)")

ggplot(data, aes(x = volume, y = factor(year), fill = factor(year))) +
  geom_boxplot(alpha = 0.5) +
  labs(title = "Distribuzione del Volume delle Vendite per Anno",
       x = "Volume (milioni $)", y = "Anno")

Commento: Il confronto per anno evidenzia l’evoluzione del mercato nel tempo. Anni con box spostati verso destra indicano volumi di vendita complessivamente più alti, segnale di un mercato in espansione. La variabilità all’interno di ogni anno (ampiezza del box) riflette le differenze stagionali e geografiche.

Grafico a Barre Sovrapposte: Vendite per Mese e Città

sales_month_city = data %>%
  group_by(month, city) %>%
  summarise(totale_sales = sum(sales), .groups = "drop")

nomi_mesi = c("Gen","Feb","Mar","Apr","Mag","Giu",
              "Lug","Ago","Set","Ott","Nov","Dic")

ggplot(sales_month_city, aes(x = factor(month, labels = nomi_mesi),
                              y = totale_sales, fill = city)) +
  geom_col() +
  labs(title = "Totale Vendite per Mese e Città (Barre Sovrapposte)",
       x = "Mese", y = "Totale Vendite", fill = "Città")

ggplot(sales_month_city, aes(x = factor(month, labels = nomi_mesi),
                              y = totale_sales, fill = city)) +
  geom_col(position = "fill") +
  scale_y_continuous(labels = percent_format()) +
  labs(title = "Composizione % delle Vendite per Mese e Città",
       x = "Mese", y = "Percentuale", fill = "Città")

Commento: Le barre sovrapposte mostrano sia il contributo di ogni città sia il totale mensile: si nota chiaramente il picco primaverile-estivo. Il grafico normalizzato rivela invece se il mix tra città cambia nel corso dell’anno: proporzioni stabili indicano che ogni città mantiene la propria quota di mercato indipendentemente dalla stagione.

Line Chart: Andamento Storico delle Vendite per Città

data$date = as.Date(paste(year, month, "01", sep = "-"))

sales_trend = data %>%
  group_by(city, date) %>%
  summarise(totale_sales = sum(sales), .groups = "drop")

ggplot(sales_trend, aes(x = date, y = totale_sales, color = city)) +
  geom_line(linewidth = 0.8) +
  labs(title = "Andamento delle Vendite nel Tempo per Città",
       x = "Data", y = "Vendite Totali", color = "Città") +
  scale_x_date(date_labels = "%Y", date_breaks = "1 year")

Commento: Il line chart rivela la stagionalità ricorrente delle vendite (picchi nei mesi estivi) e le tendenze di lungo periodo per ogni città. Le città con linea in crescita nel tempo sono mercati in espansione strutturale. La sovrapposizione delle linee permette di confrontare direttamente il peso relativo di ogni città nel corso degli anni.


Step 9: Conclusioni

L’analisi descrittiva ha evidenziato come il mercato immobiliare texano sia caratterizzato da una variabilità significativa, concentrata soprattutto nel volume delle vendite — la variabile più dispersa (CV ≈ 54%) e più asimmetrica (skewness ≈ 0.88) — mentre il prezzo mediano risulta la variabile più stabile e affidabile per il riferimento di mercato. Le vendite mostrano una chiara stagionalità con picchi primaverili ed estivi, e le città presentano comportamenti eterogenei sia in termini di prezzi che di efficacia delle inserzioni, confermando che il mercato texano non può essere letto in modo uniforme ma richiede un approccio localizzato.

Raccomandazioni principali: