Progetto Analisi Mercati Immobiliari Texas

L’azienda Texas Realty Insights 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

  1. Identificare e interpretare i trend storici delle vendite immobiliari in Texas.
  2. Valutare l’efficacia delle strategie di marketing delle inserzioni immobiliari.
  3. Offrire una rappresentazione grafica dei dati che evidenzi la distribuzione dei prezzi e delle vendite tra città, mesi e anni.

0. Dataset da analizzare e import librerie

Caricamento del dataset da analizzare e descrivere, e delle librerie necessarie per condurre l’analisi. A seguire, una preview delle prime 5 righe del dataset.

library(ggplot2)
library(moments)
library(patchwork)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ lubridate 1.9.4     ✔ tibble    3.3.0
## ✔ purrr     1.1.0     ✔ tidyr     1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
dati <- read.csv("realestate_texas.csv",sep = ",") 
knitr::kable(head(dati,5))
city year month sales volume median_price listings months_inventory
Beaumont 2010 1 83 14.162 163800 1533 9.5
Beaumont 2010 2 108 17.690 138200 1586 10.0
Beaumont 2010 3 182 28.701 122400 1689 10.6
Beaumont 2010 4 200 26.819 123200 1708 10.6
Beaumont 2010 5 202 28.833 123100 1771 10.9
attach(dati)

Il dataset contiene le seguenti variabili:

  • city: città di riferimento
  • year: anno di riferimento
  • month: mese di riferimento
  • sales: numero totale di vendite
  • volume: valore totale delle vendite (in milioni di dollari)
  • median_price: prezzo mediano di vendita (in dollari)
  • listings: numero totale di annunci attivi
  • months_inventory: quantità di tempo necessaria per vendere tutte le inserzioni correnti, espresso in mesi

1. Analisi delle variabili

Identifica e descrivi il tipo di variabili statistiche presenti nel dataset. Valuta come gestire le variabili che sottintendono una dimensione tempo e commenta sul tipo di analisi che può essere condotta su ciascuna variabile.

Iniziamo con un po’ di analisi ad alto livello: numerosità del dataset, check sulla presenza di duplicati e funzione str() per avere una prima overview delle variabili del dataset.

N <- dim(dati) [1]
cat("Il dataset contiene",N,"osservazioni\n")
## Il dataset contiene 240 osservazioni
duplicati <- dati %>% group_by_all() %>% filter(n() > 1) %>% ungroup()
cat("Il dataset contiene",dim(duplicati) [1] ,"duplicati\n") #non ci sono duplicati
## Il dataset contiene 0 duplicati

Non sono presenti duplicati altrimenti avremmo dovuto identificare, interpretare e gestire tali dati.

str(dati)
## '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 ...

Analizzando il tipo di variabile abbiamo:

  • city: var. qualitativa su scala nominale
  • year: var quantitativa discreta su scala di intervalli
  • mese: var quantitativa discreta su scala di rapporti (potrebbe anche essere una variabile qualitativa, in questo caso è codificata/indicizzata)
  • sale: var quantitativa discreta
  • volume: var quantitativa continua
  • median_price: var quantitativa continua
  • listing: var quantitativa discreta
  • months_inventory: var quantitativa continua

Risulta una sola variabile qualitatita/categorica (su scala nominale) che è ‘city’

Ci son due variabili, ossia ‘year’ e ‘month’, che tracciano nel tempo le vendite e le altre caratterstiche associate ad ogni osservazione. Per gestire al meglio la variabile tempo creaiamo una ulteriore variabile temporale che chiameremo ‘date’ e che prenderà 1 come giorno per convenzione.

dati$date <- as.Date(paste(year, month, 1, sep = "-"))
knitr::kable(head(dati, 5))
city year month sales volume median_price listings months_inventory date
Beaumont 2010 1 83 14.162 163800 1533 9.5 2010-01-01
Beaumont 2010 2 108 17.690 138200 1586 10.0 2010-02-01
Beaumont 2010 3 182 28.701 122400 1689 10.6 2010-03-01
Beaumont 2010 4 200 26.819 123200 1708 10.6 2010-04-01
Beaumont 2010 5 202 28.833 123100 1771 10.9 2010-05-01
str(dati)
## 'data.frame':    240 obs. of  9 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 ...
##  $ date            : Date, format: "2010-01-01" "2010-02-01" ...
attach(dati)
## The following objects are masked from dati (pos = 3):
## 
##     city, listings, median_price, month, months_inventory, sales,
##     volume, year

2. Indici di posizione, variabilità e forma

Calcola Indici di posizione, variabilità e forma per tutte le variabili per le quali ha senso farlo, per le altre crea una distribuzione di frequenza. Infine, commenta tutto brevemente.

Per ‘city’, ‘year’ e ‘month’ calcoliamo le frequenze assolute dal momento che le variabili in analisi hanno realizzazioni che appartengono ad un insieme finito di valori.

distr_freq_city<-as.data.frame(
  cbind(
    ni=table(city),
    fi=table(city)/N))
knitr::kable(distr_freq_city)
ni fi
Beaumont 60 0.25
Bryan-College Station 60 0.25
Tyler 60 0.25
Wichita Falls 60 0.25
distr_freq_year<-as.data.frame(
  cbind(
    ni=table(year),
    fi=table(year)/N))
knitr::kable(distr_freq_year)
ni fi
2010 48 0.2
2011 48 0.2
2012 48 0.2
2013 48 0.2
2014 48 0.2
distr_freq_month<-as.data.frame(
  cbind(
    ni=table(month),
    fi=round(table(month)/N,2)))
knitr::kable(distr_freq_month)
ni fi
20 0.08
20 0.08
20 0.08
20 0.08
20 0.08
20 0.08
20 0.08
20 0.08
20 0.08
20 0.08
20 0.08
20 0.08

Abbiamo una equa distribuzione di osservazioni per ogni città, anno e mese.

Per le restanti variabili si può utilizzare la funzione summary() per avere una prima overview (in realtà facciamo su tutte le variabili per avere anche gli indici di posizione di ‘year’ e ‘month’)

knitr::kable(summary(dati))
city year month sales volume median_price listings months_inventory date
Length:240 Min. :2010 Min. : 1.00 Min. : 79.0 Min. : 8.166 Min. : 73800 Min. : 743 Min. : 3.400 Min. :2010-01-01
Class :character 1st Qu.:2011 1st Qu.: 3.75 1st Qu.:127.0 1st Qu.:17.660 1st Qu.:117300 1st Qu.:1026 1st Qu.: 7.800 1st Qu.:2011-03-24
Mode :character Median :2012 Median : 6.50 Median :175.5 Median :27.062 Median :134500 Median :1618 Median : 8.950 Median :2012-06-16
NA Mean :2012 Mean : 6.50 Mean :192.3 Mean :31.005 Mean :132665 Mean :1738 Mean : 9.193 Mean :2012-06-16
NA 3rd Qu.:2013 3rd Qu.: 9.25 3rd Qu.:247.0 3rd Qu.:40.893 3rd Qu.:150050 3rd Qu.:2056 3rd Qu.:10.950 3rd Qu.:2013-09-08
NA Max. :2014 Max. :12.00 Max. :423.0 Max. :83.547 Max. :180000 Max. :3296 Max. :14.900 Max. :2014-12-01

Il summary() ci fornisce in maniera compatta la media, mediana, 1° e 3° quartile, minimo e massimo. Per analizzare al meglio questi indici e per valutare anche gli indici di variabilità e forma, possiamo farne una rappresentazione grafica.

indici_vis <- function(data, feature){
  
  #Gestione dati e label
  feature_name <- deparse(substitute(feature))  # nome come stringa
  values <- pull(data, {{feature}})  # estrae il vettore
  
  # Calcoli statistici
  mu <- mean(values, na.rm = TRUE)
  sigma <- sd(values, na.rm = TRUE)
  cv <- sigma / mu
  sk <- skewness(values, na.rm = TRUE)
  ku <- kurtosis(values, na.rm = TRUE) - 3  # kurtosi centrata
  
  # Crea df per la label informativa
  info_label <- data.frame(x = mu, y = 0,
                            label = paste0("μ = ", round(mu,2),
                                           "\nσ = ", round(sigma,2),
                                           "\nCV = ", round(cv,2),
                                           "\nSkew = ", round(sk,2),
                                           "\nKurt = ", round(ku,2)))
  
  # Quantili
  q_vals <- quantile(values, probs = c(0.25, 0.5, 0.75), na.rm = TRUE)
  mean_val <- mean(values, na.rm = TRUE)
  
  # Plot dei record ordinati
  record_plot <- ggplot() + 
    geom_point(aes(x = seq_along(values), y = sort(values))) +
    geom_hline(yintercept = mu, color = "blue", linetype = "dashed", linewidth = 0.7) +
    geom_hline(yintercept = q_vals, color = "red", linetype = "dotted" , linewidth = 0.7) +
    geom_label(aes(x = length(values)*0.9, y = mu),
               label = paste0("Media: ", round(mu, 1)),
               color = "blue", size = 3) +
    geom_label(data = data.frame(y = q_vals, label = names(q_vals)),
               aes(x = length(values)*0.9, y = y, label = paste0(label, ": ", round(y, 1))),
               color = "red", size = 3) +
    labs(title = "Distribuzione osservazioni ordinate",
         x = "n. osservazione", y = "Valore osservazione") +
    theme_minimal()
  
  # Boxplot
  box_plot <- ggplot(data, aes(y = {{feature}})) +
    geom_boxplot(fill = "tan1") +
    geom_hline(yintercept = mu, color = "blue", linetype = "dashed", linewidth = 0.7) +
    geom_hline(yintercept = q_vals, color = "red", linetype = "dotted", linewidth = 0.7) +
    labs(title = "Boxplot",
         y = "Valore osservazione") +
    theme_minimal()+
    theme(axis.text.x = element_blank(),  
        axis.ticks.x = element_blank())
  
  # Grafico della densità con box dei valori
  density_plot <- ggplot(data, aes(x = {{feature}})) +
    geom_density(fill = "lightblue", color = "black", alpha = 0.6) +
    geom_vline(xintercept = mu, color = "blue", linetype = "dashed", linewidth = 0.7) +
    geom_label(data = info_label, aes(x = x, y = y, label = label),
               hjust = -0.1, vjust = -0.1, size = 3, fill = "white", label.size = 0.3) +
    labs(title = "Stima della densità di probabilità",
         x = "Valore osservazione", y = "Densità osservazione") +
    theme_minimal() 
  
  print((record_plot + box_plot) + 
          plot_annotation(title = paste("Analisi di:", feature_name,"\n"), 
                          theme = theme(plot.title = element_text(size = 16))))
  print(density_plot)
} 

indici_vis(dati,sales) 

indici_vis(dati,volume)

indici_vis(dati,median_price)

indici_vis(dati,listings)

indici_vis(dati,months_inventory)

Analizzando i grafici possiamo fare le seguenti considerazioni:

  • ‘sales’ e ‘volume’ nonostante visivamente sembrino simili hanno punti in comune e differenze concrete. Entrambe le variabili hanno distribuzioni sbilanciate e con asimmetria positiva come testimoniato dalla skewness. Di contro, ‘volume’ ha una maggiore variabilità (come si evince dal confronto del coeff. di variazione) e una curva leptocurtica rispetto alla curva platicurtica di ‘sales’ (come testimoniato dalle curtosi calcolate). Infine, si fa notare che ‘volume’ emerge tra tutti per la presenza evidente di outlier (come si vede da boxplot)
  • ‘median_price’ e ‘month_inventory’ mostrano una distribuzione di densità centrata. In particolare, emerge ‘month_inventory’ con il suo valore di skewness prossimo allo zero (0.04). Allo stesso tempo, le due variabili in questione sono anche le variabili con il minore variabilità. Se si prende come riferimento il coefficiente di variazione, son le uniche due variabili con un coefficiente calcolato inferiore a 0.25 mentre le altre hanno valori maggiori di 0.41
  • ‘listings’ mostra una distribuzione di densità di probabilità molto particolare con un comportamento bimodale. Presenta 3 massimi di densità di probabilità di cui il massimo assoluto si avvicina molto alla media. A seguire la curva mostra un minimo intorno ai valori di ‘listings’ nell’intorno di 2300 e un nuovo massimo relativo intorno a 2800. Si fa notare che ‘listing’ è anche la variabile con la maggiore curtosi negativa (curva fortemente platicurtica).

Prima di andare al punto successivo, scriviamoci una funzione che calcola e restituisci alcuni indici degli indici analizzati e che ci serviranno nel corso dell’analisi.

indici_calc <- function(data, feature){
  
  #Gestione dati e label
  feature_name <- deparse(substitute(feature))  # nome come stringa
  values <- pull(data, {{feature}})  # estrae il vettore
  
  # Calcoli statistici
  mu <- mean(values, na.rm = TRUE)
  sigma <- sd(values, na.rm = TRUE)
  cv <- sigma / mu
  sk <- skewness(values, na.rm = TRUE)
  ku <- kurtosis(values, na.rm = TRUE) - 3  # kurtosi centrata
  
  return(c(mu = mu, sigma = sigma, cv = cv, skewness = sk, kurtosis = ku))
}

3. Identificazione delle variabili con maggiore variabilità e assimmetria

Determina: 1) Qual è la variabile con la più alta variabilità; 2) Qual è la variabile con la distribuzione più asimmetrica Spiega come sei giunto a queste conclusioni e fornisci considerazioni statistiche.

Per la variabilità consideriamo il coefficiente di variazione per avere un valore normalizzato che tenga conto della media e della varianza. Tale coefficiente rende confrontabile la variabilità tra due variabili di uno stesso campione o la variabilità di due campioni relativamente alla stessa variabile.

Si fa notare che è possibile utilizzare il coefficiente di variazione poiché le variabili da confrontare non hanno media nulla.

Per l’asimmetria utilizzeremo la skewness, indicatore di asimmetria per eccellenza.

# Calcolo gli indici per ogni feature
sales_stats  <- indici_calc(dati, sales)
volume_stats <- indici_calc(dati, volume)
median_price_stats <- indici_calc(dati, median_price)
listings_stats     <- indici_calc(dati, listings)
months_inventory_stats <- indici_calc(dati, months_inventory)

# Inserisco i valori in un df
comparazione_df <- rbind(
  sales            = sales_stats,
  volume           = volume_stats,
  median_price     = median_price_stats,
  listings         = listings_stats,
  months_inventory = months_inventory_stats
)

comparazione_df <- as.data.frame(comparazione_df)

# Mostro i risultati
knitr::kable(round(comparazione_df, 2))
mu sigma cv skewness kurtosis
sales 192.29 79.65 0.41 0.72 -0.31
volume 31.01 16.65 0.54 0.88 0.18
median_price 132665.42 22662.15 0.17 -0.36 -0.62
listings 1738.02 752.71 0.43 0.65 -0.79
months_inventory 9.19 2.30 0.25 0.04 -0.17
# Individua massimi
max_variabilita  <- rownames(comparazione_df)[which.max(comparazione_df$cv)]
cat("Feature con massima variabilità (CV):", max_variabilita, "\n")
## Feature con massima variabilità (CV): volume
max_asimmetria   <- rownames(comparazione_df)[which.max(abs(comparazione_df$skewness))]
cat("Feature con massima asimmetria (Skewness):", max_asimmetria, "\n")
## Feature con massima asimmetria (Skewness): volume

Il volume è la variabile che risulta avere la maggiormente variabilità in termini di coefficiente di variazione (0.54) e la maggiore asimmetria in termini di skewness (0.88). Tale risultato, se riprendiamo i risultati del punto 2, è dovuto anche alla presenza di outliers.

outliers <- volume[volume > quantile(volume, 0.99)]
N_outliers <- length(outliers)
cat("Ci sono", N_outliers, "outliers (soglia 99%) che sono:", outliers)
## Ci sono 3 outliers (soglia 99%) che sono: 77.983 83.547 80.814

4. Creazione di classi per una variabile quantitativa

Seleziona una variabile quantitativa (es. sales o median_price) e suddividila in classi. Crea una distribuzione di frequenze e rappresenta i dati con un grafico a barre. Calcola l’indice di eterogeneità Gini e discuti i risultati.

Sarà analizzata la variabile ‘sales’. Il primo step prevede la scelta del numero di intervalli da cui dispende la deinizione delle classi e di conseguenza l’indice di eterogenicità. Da notare che un numero di intervalli troppo elevato potrebbe definire delle classi con pochissime osservazioni, condizione non desiderata per questa analisi.

# Suddivisione in classi
num_bin = 4
dati$sales_cl <- cut(dati$sales, breaks = seq(from = min(sales), 
                                              to = max(sales), 
                                              by = (max(sales)-min(sales))/num_bin))

# Distribuzione di frequenze 
ni = table(dati$sales_cl)
fi = round(ni/N,2)
Ni <- round(cumsum(ni),2)
Fi <- round(Ni/N,2)
F2 <- round(table(dati$sales_cl)/length(dati$sales_cl),2)
distr_frequenze <- as.data.frame(cbind(ni,fi,Ni,Fi,F2))
knitr::kable(distr_frequenze)
ni fi Ni Fi F2
(79,165] 108 0.45 108 0.45 0.45
(165,251] 71 0.30 179 0.75 0.30
(251,337] 45 0.19 224 0.93 0.19
(337,423] 14 0.06 238 0.99 0.06
#Grafico a barre
barplot(distr_frequenze$ni,
        xlab = "Classi di sales",
        ylab = "Frequenze assolute",
        names.arg = rownames(distr_frequenze),
        ylim = c(0,120),
        col="blue")

# Indice di Gini
gini.index <- function(x){ #x: var qualitativa
  ni = table(x)
  fi = ni/length(x)
  fi2 = fi^2
  J = length(table(x))
  
  gini = 1-sum(fi2)
  gini.norm = gini/((J-1)/J)
  
  return(round(gini.norm,2))
}


gini.index(dati$sales_cl)
## [1] 0.9

Per un numero di classi pari a 4 abbiamo un num di Gini pari a 0.9, indice di un’alta eterogenicità e propensione ad assumere le diverse modalità che può assumere la variabile.

Il valore dipende anche dal numero di classi:

  • per un numero di classi pari a 2, ossia il minimo, l’indice risulta essere 0.77 (comunque un valore alto)
  • per un numero di classi pari a 6, l’indice di Gini sale a 0.93

Alla luce di questi ultimi test con diversi numeri di classi, si può affermare che l’indice di Gini per la variabile ‘sales’ dimostra un’alta eterogenicità (anche se lontani da una equidistribuzione (indice di Gini pari a 1) come si evince anche dal grafico precedente).

5. Calcolo della probabilità

Qual è la probabilità che, presa una riga a caso di questo dataset, essa riporti la città “Beaumont”?

prop_beaumont <- mean(city == "Beaumont")
prop_beaumont
## [1] 0.25

In accordo alla definizione di probabilità classica (num. casi favorevoli/num. casi possibili totali), la probabiità di prescare Beaumont è di 1/4

E la probabilità che riporti il mese di Luglio?

prop_luglio <- round(mean(month == 7),2)
prop_luglio
## [1] 0.08

In accordo alla definizione di probabilità classica (num. casi favorevoli/num. casi possibili totali), la probabiità di prescare il mesi di leglio è di 1/12

E la probabilità che riporti il mese di dicembre 2012?

prop_dic2012 <- round(mean(format(date, "%Y-%m") == "2012-12"),2)
prop_dic2012
## [1] 0.02

In accordo alla definizione di probabilità classica (num. casi favorevoli/num. casi possibili totali) e sotto l’ipotesi di indipendenza dei due eventi, la probabiità di prescare il mese di dicembre 2012 è (1/12)*(1/5), ossia la moltiplicazione della probabilità di pescare il mese di dicembre e l’anno 2012. Il tutto si basa sull’assunto che l’estrazione dal dataset del mese e dell’anno siano indipendenti, ma arriveremmo allo stesso risultato anche se li considerassimo accoppiati: in questo caso si hanno 5x12 combinazioni e una probabilità di 1/60 di estrarre un certo mese di un certo anno.

6. Creazione di nuove variabili

Crea una nuova colonna che calcoli il prezzo medio degli immobili utilizzando le variabili disponibili. Prova a creare una colonna che misuri l’efficacia degli annunci di vendita. Commenta e discuti i risultati

Prezzo medio calcolato come valore totale delle vendite su num di vendite totali. Quindi usando le variabili a disposizione: volume su sales.

dati$mean_price <- dati$volume*10e6 / dati$sales
knitr::kable(head(dati,5))
city year month sales volume median_price listings months_inventory date sales_cl mean_price
Beaumont 2010 1 83 14.162 163800 1533 9.5 2010-01-01 (79,165] 1706265
Beaumont 2010 2 108 17.690 138200 1586 10.0 2010-02-01 (79,165] 1637963
Beaumont 2010 3 182 28.701 122400 1689 10.6 2010-03-01 (165,251] 1576978
Beaumont 2010 4 200 26.819 123200 1708 10.6 2010-04-01 (165,251] 1340950
Beaumont 2010 5 202 28.833 123100 1771 10.9 2010-05-01 (165,251] 1427376

Per l’efficacia occorre ragionare sulle variabili in gioco:

ggplot(dati, aes(x = date, y = listings, color = city)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Annunci attivi nel tempo",
       x = "Tempo",
       y = "Annunci attivi, n.",
       col = "Città") +
  theme_minimal() 
## `geom_smooth()` using formula = 'y ~ x'

# Gli annunci attivi mostrano un trend decrescente e periodico

ggplot(dati, aes(x = date, y = months_inventory, color = city)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Mesi necessari per vendere annunci nel tempo",
       x = "Tempo",
       y = "Mesi di giacenza annuncio",
       col = "Città") +
  theme_minimal() 
## `geom_smooth()` using formula = 'y ~ x'

# anche i tempi di giacenza diminuiscono sensibilmente negli ultimi anni suggerendo un aumento delle vendite

ggplot(dati, aes(x = date, y = sales, color = city)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Vendite nel tempo",
       x = "Tempo",
       y = "Vendite, n.",
       col = "Città") +
  theme_minimal() 
## `geom_smooth()` using formula = 'y ~ x'

# 3/4 città vedono un incremento sostanziale delle vendite

# quindi leghiamo sales e annunci
ggplot(dati, aes(x = date, y = sales*100/listings, color = city)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Efficacia annunci nel tempo",
       x = "Tempo",
       y = "Vendite/Annunci attivi, %",
       col = "Città") +
  theme_minimal() 
## `geom_smooth()` using formula = 'y ~ x'

# sembra che nel tempo ci sia un aumento di rapporto tra numero di vendite e annunci nel tempo

L’analisi potrebbe continuare con un approfondimento sulla correlazione delle variabili per cercare di capire quali sono le variabili che concorrono a far aumentare l’efficienza degli annunci e come si legano listings e sales.

Per ora, ci limitiamo a definire l’efficacia degli annunci come sales/listings e dire che aumenta nel tempo.

dati$listing_efficiency <- dati$sales / dati$listings
knitr::kable(head(dati,5))
city year month sales volume median_price listings months_inventory date sales_cl mean_price listing_efficiency
Beaumont 2010 1 83 14.162 163800 1533 9.5 2010-01-01 (79,165] 1706265 0.0541422
Beaumont 2010 2 108 17.690 138200 1586 10.0 2010-02-01 (79,165] 1637963 0.0680958
Beaumont 2010 3 182 28.701 122400 1689 10.6 2010-03-01 (165,251] 1576978 0.1077561
Beaumont 2010 4 200 26.819 123200 1708 10.6 2010-04-01 (165,251] 1340950 0.1170960
Beaumont 2010 5 202 28.833 123100 1771 10.9 2010-05-01 (165,251] 1427376 0.1140599
indici_vis(dati, listing_efficiency)

A questo punto riassiumiamo il trend dell’efficacia usando la media.

stats_efficiecy <- dati %>%
  group_by(year) %>%
  summarise(
    mean_eff = round(mean(listing_efficiency, na.rm = TRUE),3),
    sd_eff = round(sd(listing_efficiency, na.rm = TRUE),3),
    .groups = "drop"
  )
knitr::kable(stats_efficiecy)
year mean_eff sd_eff
2010 0.100 0.034
2011 0.093 0.023
2012 0.110 0.028
2013 0.135 0.045
2014 0.157 0.062
ggplot(stats_efficiecy, aes(x = year, y = mean_eff)) +
  geom_line() +
  geom_point() +
  geom_errorbar(aes(ymin = mean_eff - sd_eff, ymax = mean_eff + sd_eff),
                width = 0.2) +
  labs(title = "Efficacia annunci media per anno", y = "Media efficacia", x = "Anno") +
  theme_minimal()

Interessante vedere come la media dell’efficacia aumenti rapidamente nel tempo passando da un valore prossimo al 10% fino a circa il 16%. Aumenta anche la deviazione standard e questa è dovuta agli outlier introdotti da Bryan-College Station.

7. Analisi condizionata

Usa il pacchetto dplyr o il linguaggio base di R per effettuare analisi statistiche condizionate per città, anno e mese. Genera dei summary (media, deviazione standard) e rappresenta graficamente i risultati.

L’analisi condotta in questo paragrafo si concetrerà sulle vendite ma potrà essere reiterata sulle altre variabili.

1) Analisi per città

stats_city <- dati %>%
  group_by(city) %>%
  summarise(
    mean_sales = round(mean(sales, na.rm = TRUE),3),
    sd_sales = round(sd(sales, na.rm = TRUE),3),
    cv_sales = round(sd_sales / mean_sales,3),
    .groups = "drop"
  )

knitr::kable(stats_city)
city mean_sales sd_sales cv_sales
Beaumont 177.383 41.484 0.234
Bryan-College Station 205.967 84.984 0.413
Tyler 269.750 61.964 0.230
Wichita Falls 116.067 22.152 0.191
ggplot(stats_city, aes(x = city, y = mean_sales, fill = city)) +
  geom_col() +
  geom_errorbar(aes(ymin = mean_sales - sd_sales, ymax = mean_sales + sd_sales),
                width = 0.2) +
  labs(title = "Vendite medie per città", y = "Media vendite", x = "Città", fill = "Città") +
  theme_minimal()

In media, le vendite in città differenti variano molto. A causa di una deviazione standard non omogenea, il confronto tra le città usando soltanto questo grafico non è esaustivo. Tuttavia, si può assermare che in media Tyler è la città che ha venduto di più mentre, al contrario, Wichita Falls quella dove ci son state meno vendite. Da notare anche che Bryan-college station è il luogo in cui vi è maggiore variabilità nelle vendite (cv maggiore di tutti).

2) Analisi per anno

stats_year <- dati %>%
  group_by(year) %>%
  summarise(
    mean_sales = round(mean(sales, na.rm = TRUE),3),
    sd_sales = round(sd(sales, na.rm = TRUE),3),
    .groups = "drop"
  )
knitr::kable(stats_year)
year mean_sales sd_sales
2010 168.667 60.537
2011 164.125 63.870
2012 186.146 70.905
2013 211.917 83.996
2014 230.604 95.515
ggplot(stats_year, aes(x = year, y = mean_sales)) +
  geom_line() +
  geom_point() +
  geom_errorbar(aes(ymin = mean_sales - sd_sales, ymax = mean_sales + sd_sales),
                width = 0.2) +
  labs(title = "Vendite medie per anno", y = "Media vendite", x = "Anno") +
  theme_minimal()

Il grafico mostra un aumento costante della media delle vendite globale dal 2011 in poi. Anche la variabilità segue lo stesso trend negli anni.

3) Analisi per mese

stats_month <- dati %>%
  group_by(month) %>%
  summarise(
    mean_sales = round(mean(sales, na.rm = TRUE),3),
    sd_sales = round(sd(sales, na.rm = TRUE),3),
    .groups = "drop"
  )

ggplot(stats_month, aes(x = factor(month), y = mean_sales)) +
  geom_col(fill = "steelblue") +
  geom_errorbar(aes(ymin = mean_sales - sd_sales, ymax = mean_sales + sd_sales),
                width = 0.2) +
  labs(title = "Vendite medie per mese", x = "Mese", y = "Media vendite") +
  theme_minimal()

Analizzando la stagionalità, dall’analisi emerge che si preferisce comprare in primavera-estate: la media delle vendite risulta più alta tra maggio e settembre.

Infine utilizziamo i boxplot o qualche variante per confrontare la distribuzione del valore totale delle vendite tra le varie città ma anche tra i vari anni

# Opzione 1
ggplot(dati, aes(x = city, y = sales, fill = city)) +
  geom_boxplot() +
  facet_wrap(~ year) +
  labs(title = "Boxplot vendite per anno, suddivise per città",y= "Vendite", x = "Anno", fill = "Città") +
  theme_minimal() +
  theme(axis.text.x = element_blank())  

Il grafico riassume un po’ le viste fornite dai grafici precedenti ma ci permette di osservare meglio l’andamento nel tempo degli indici di posizione e variabilità riferiti alle vendite per ogni città. Si fa notare che la forte variabilità delle vendite di Bryan-college notata in precedenza, viene amplificata fortemente dal 2012 in poi e con dei range interquartili che raggiungono i massimi valori nel 2012 e 2013.

# Opzione 2
stats_city_year <- dati %>%
  group_by(city, year) %>%
  summarise(
    mean_sales = mean(sales, na.rm = TRUE),
    sd_sales = sd(sales, na.rm = TRUE),
    .groups = "drop"
  )

ggplot(stats_city_year, aes(x = city, y = mean_sales, fill = factor(year))) +
  geom_col(position = "dodge") +
  geom_errorbar(aes(ymin = mean_sales - sd_sales, ymax = mean_sales + sd_sales),
                position = position_dodge(width = 0.9), width = 0.2) +
  labs(title = "Barplot vendite medie per città, suddivise per anno",
       y = "Media vendite", x = "Città", fill = "Anno") +
  theme_minimal()

Gli ultimi grafici non fanno altro che confermare quanto osservato in precedenza. Le medie delle vendite crescono nel tempo e in quasi tutte le città (a meno di Witchita Falls).

8. Creazione di visualizzazioni con ggplot2

Utilizza ggplot2 per creare grafici personalizzati. Assicurati di esplorare:

  • Boxplot per confrontare la distribuzione del prezzo mediano tra le città.
  • Grafici a barre per confrontare il totale delle vendite per mese e città.
  • Line charts per confrontare l’andamento delle vendite in periodi storici differenti.
  1. Boxplot per confrontare la distribuzione del prezzo mediano tra le città.
ggplot(dati, aes(x = city, y = median_price, fill = city)) +
  geom_boxplot() +
  labs(title = "Boxplot prezzo mediano vendite", y = "Prezzo mediano, $", x = "Città", fill = "Città") +
  theme_minimal()

stats_medianprice <- dati %>%
  group_by(city) %>%
  summarise(
    mean_sales = round(mean(median_price, na.rm = TRUE),3),
    sd_sales = round(sd(median_price, na.rm = TRUE),3),
    .groups = "drop"
  )
knitr::kable(stats_medianprice)
city mean_sales sd_sales
Beaumont 129988.3 10104.993
Bryan-College Station 157488.3 8852.235
Tyler 141441.7 9336.538
Wichita Falls 101743.3 11320.034

Il prezzo mediano differisca molto in termini di media e mediana da città a città. Sulla base di questi indici, Bryan-College Station è la città con il prezzo mediano più alto (e la deviazione standard minone) mentre Wichita Falls quella con il più basso (e la deviazione standard maggiore).

Vediamo cosa succede se aggiungiamo l’informazione tempo.

ggplot(dati, aes(x = factor(year), y = median_price, fill = city)) +
  geom_boxplot() +
  labs(title = "Distribuzione del prezzo mediano per anno, suddiviso per città",
       x = "Anno", y = "Prezzo mediano, $" , fill = "Città") +
  theme_minimal()

Viene riconfermata la posizione di Bryan-College Station in cima alla classifica delle città con prezzo mediano più elevato e questa volta anche negli anni. A questo si aggiunge un trend di crescita del prezzo mediano nel tempo anche per Tyler.

  1. Grafici a barre per confrontare il totale delle vendite per mese e città.
vendite_mensili <- dati %>%
  group_by(city, month, year) %>%
  summarise(tot_sales = sum(sales, na.rm = TRUE), .groups = "drop")

ggplot(vendite_mensili, aes(x = factor(month), y = tot_sales, fill = city)) +
  geom_col(position = "dodge") +
  labs(title = "Totale vendite per mese (e città su barre affiancate)",
       x = "Mese", y = "Totale vendite", fill = "Città") +
  theme_minimal()

ggplot(vendite_mensili, aes(x = factor(month), y = tot_sales, fill = city)) +
  geom_col(position = "stack") +
  labs(title = "Totale vendite per mese (e città su barre sovrapposte)",
       x = "Mese", y = "Totale vendite", fill = "Città") +
  theme_minimal()

vendite_mensili_norm <- vendite_mensili %>%
  group_by(month) %>%
  mutate(perc_sales = tot_sales / sum(tot_sales))

ggplot(vendite_mensili_norm, aes(x = factor(month), y = perc_sales, fill = city)) +
  geom_col(position = "fill") +
  labs(title = "Distribuzione percentuale delle vendite per mese (e città su barre normalizzate)",
       x = "Mese", y = "Percentuale vendite", fill = "Città") +
  scale_y_continuous(labels = scales::percent) +
  theme_minimal()

C’è una certa tendenza a vendere maggiormente tra i mesi di maggio e settembre. La tendenza risulta molto marcata nelle città di Bryan-College Station e Tyler (come evidenziato anche dalla distribuzione delle percentuali delle vendite). E negli anni?

ggplot(vendite_mensili, aes(x = factor(month), y = tot_sales, fill = city)) +
  geom_col(position = "dodge") +
  facet_wrap(~ year) +
  labs(title = "Totale vendite per mese visto negli anni",
       x = "Mese", y = "Totale vendite", fill = "Città") +
  theme_minimal()+
  theme(axis.text.x = element_blank(),  
        axis.ticks.x = element_blank())

Negli anni si vede che tale comportamento si ripete ad eccezione del 2010 in cui la maggior parte delle vendite si son concentrate tra marzo e maggio.

  1. Line charts per confrontare l’andamento delle vendite in periodi storici differenti.
andamento_vendite <- dati %>%
  group_by(city, date) %>%
  summarise(tot_sales = sum(sales, na.rm = TRUE), .groups = "drop")

ggplot(andamento_vendite, aes(x = date, y = tot_sales, color = city)) +
  geom_line(linewidth = 1) +
  geom_smooth(method = "lm", se = FALSE, linetype = "dotted") +
  labs(title = "Andamento delle vendite totali nel tempo",
       x = "Tempo", y = "Totale vendite", col = "Città") +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

Oltre ad una certa stagionalità delle vendite, dall’analisi del grafico emerge un aumento delle vendite negli anni. In particolare per le città di Bryan-College Station e Tyler, ma anche in Beaumont.

Combinando le osservazioni tratte dai precedenti studi, ci si chiede se un elevato prezzo mediano sia collegato in qualche modo ad un maggiore volume di vendite. Vedi i casi di Bryan-College Station e Tyler.

9. Conclusioni

Fornisci una sintesi dei risultati ottenuti, facendo riferimento alle principali tendenze emerse e fornendo raccomandazioni basate sull’analisi. Questo non è un progetto di programmazione, ma di statistica, e ci si aspetta di leggere commenti e considerazioni statistiche per i vari passaggi e risultati.

L’analisi presentata nel seguente progetto ha come obiettivo quello di supportare l’azienda Texas Realty Insights nell’analisi delle tendenze del mercato immobiliare nello stato del Texas tramite insight statistici e visivi.

In tale paragrafo conclusivo verrà fornita una sintesi dei risultati orientata agli obiettivi principali del progetto.

A) Identificare e interpretare i trend storici delle vendite immobiliari in Texas.

Dal 2011 si vede un aumento costante delle vendite nel tempo nonostante un aumento della variabilità delle vendite stesse: la media delle vendite subisce un incremento costante così come la deviazione standard fino a raggiungere i loro relativi massimi nel 2014.

In merito alle 4 città analizzate, emergono dei pattern di vendita positivi a meno di Wichita Falls, unica città ad avere un trend piatto: le altre 3 città, ossia Beaumont, Bryan-College Station e Tyler, hanno una media di vendite maggiore e, sopratutto, una media che aumenta nel tempo dal 2011 in poi.

Tra queste Tyler è la città con il maggior numero di vendite (presenta i più alti valori di vendite in media e mediana negli ultimi 5 anni) mentre Bryan-College Station è la città con il prezzo mediano di vendite più alto (accompagnato anche dall’avere la deviazione standard del prezzo mediano minore rispetto alle altre città).

I dati analizzati ci dicono quindi che Bryan-College Station e Tyler son due città in cui si ha un mercato immobiliare con delle tendenze molto positive sia in termini di numero di vendite che di prezzo mediano delle vendite. Tale primato viene confermato anche dall’aumento costante del prezzo mediano dal 2011 in poi nelle due città.

B) Valutare l’efficacia delle strategie di marketing delle inserzioni immobiliari.

Per quanto riguarda le inserzioni immobiliari, l’analisi rivela un impatto positivo delle strategie di marketing adottare negli anni. Si può osservare una riduzione degli annunci e della quantità di tempo necessaria per vendere le inserzioni correnti (il tutto sempre accompagnato da una crescita delle vendite)

Se si considera l’efficacia delle strategie di marketing come il rapporto tra il numero di vendite e il numero di annunci attivi, è possibili osservare un trend crescente nel tempo (a meno di Wichita Falls, unica città a mostrare un trend piatto). Nel 2014 l’efficacia raggiunge il suo valore massimo (la media passa dal 10% al 16%). Sarebbe interessante, supportati da dati relativi al marketing, capire a cosa è correlato questo trend.

Inoltre, osservando meglio il trend si notano delle fluttuazioni periodiche. Tali periodicità sono legate alla stagionalità delle vendite. Si preferisce comprare in primavera-estate, tra maggio è settembre. La distribuzione delle vendite rispetto ai 12 mesi risulta essere centrata.

Questo è particolarmente vero per Bryan-College Station e Tyler e negli anni dal 2011 in poi.