# chiamo le librerie
suppressPackageStartupMessages(library(readr))
suppressPackageStartupMessages(library(dplyr))
suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(knitr))

# carico i dati
data <-read.csv("realestate_texas.csv")

# converto i dati in formato df
re_texas <- as.data.frame(data)

1.ANALISI DELLE VARIABILI

city: variabile qualitativa nominale -> adatta a un’analisi delle frequenze per valutare l’andamento delle vendite nelle singole città

month: variabile qualitativa nominale (ciclica), codificata in numeri -> adatta ad analisi temporale

year: variabili quantitativa continua da trattare come qualitativa ordinale in questo caso

volume, median_pice: variabili quantitative continue

sales, listings: variabili quantitative discrete -> adatta per il calcolo delle frequenze e per il calcolo degli indici di posizione, variabilità e forma

months_inventory: variabile quantitative continue: adatta per il calcolo degli indici di posizione, variabilità e forma

Le quantitative continue sono tutte su scala di rapporti

2. INDICI DI POSIZIONE, VARIABILITÀ E FORMA

2.1

# inizio definendo una funzione per automatizzare la creazione delle tabelle di frequenze
tab_freq <- function(data, v, x){
  
  # trovo il numero delle occorrenze totali
  N <- dim(data)[1]
  # ottengo le frequenze assolute creando il DataFrame
  df_dist_frq <- as.data.frame(table(v))
  # rinomino la prima colonna
  colnames(df_dist_frq)[1] <- x
  # rinomino la seconda colonna 
  colnames(df_dist_frq)[2] <- 'freq_ass'
  # Calcolo le frequenze relative (Ora funziona perché ret_freq_ass è un numero!)
  df_dist_frq$freq_rel <- round(df_dist_frq$freq_ass / N, 4)
  # Calcolo le percentuali
  df_dist_frq$freq_pct <- round(df_dist_frq$freq_rel * 100, 2)
  # Ritorno il dataframe finale
  df_dist_frq
}

# chiamo la funzione su city, year e month
kable(tab_freq(re_texas, re_texas$city, 'Città'), row.names = FALSE)
Città freq_ass freq_rel freq_pct
Beaumont 60 0.25 25
Bryan-College Station 60 0.25 25
Tyler 60 0.25 25
Wichita Falls 60 0.25 25
kable(tab_freq(re_texas, re_texas$year, 'Anno'), row.names = FALSE)
Anno freq_ass freq_rel freq_pct
2010 48 0.2 20
2011 48 0.2 20
2012 48 0.2 20
2013 48 0.2 20
2014 48 0.2 20
kable(tab_freq(re_texas, re_texas$month, 'Mese'), row.names = FALSE)
Mese freq_ass freq_rel freq_pct
1 20 0.0833 8.33
2 20 0.0833 8.33
3 20 0.0833 8.33
4 20 0.0833 8.33
5 20 0.0833 8.33
6 20 0.0833 8.33
7 20 0.0833 8.33
8 20 0.0833 8.33
9 20 0.0833 8.33
10 20 0.0833 8.33
11 20 0.0833 8.33
12 20 0.0833 8.33
# definisco una funzione per automatizzare la visualizzazione
freq_viz <- function(data, v, x){
  ggplot(data)+
    geom_bar(aes(x=v),
             fill = 'red',
             colour = 'black')+
    labs(title=paste('Frequenze Assolute di', x), x=x, y='Frequenze')+
    theme_classic()
}

# chiamo la funzione su tutte e tre le colonne
freq_viz(re_texas, re_texas$city, 'Città')

freq_viz(re_texas, re_texas$year, 'Anno')

freq_viz(re_texas, re_texas$month, 'Mese')

ANALISI FINALE:

Le variabili ‘city’, ‘year’ e ‘month’ hanno natura differente in quanto ‘city’ e ‘month’ sono qualitative nominale, mentre year quantitativa continua.Tutte e tre presentano una distribuzione uniforme, le modalità non presentano sbilanciamenti evidenti

2.2

# definisco una funzione che calcola le statistiche
eda <- function(v){
  # creo le variabili media, std e coefficiente di variazione
  media <- mean(v, na.rm = TRUE)
  std <- sd(v, na.rm = TRUE)
  coef_var <- (std/media)*100
  # creo un vettore nel quale salvo tutti i risultati
  # senza ottenevo solo risultati singoli.
  # così sono incapsulati in un unico oggetto e vanno insieme a stampa
  # creo il df per mostrare le stats in modo ordinato
  var_stats = data.frame(
    etichette = c(
    "Min", "Max", "Mediana", "Media", "SD", "IQR", "CV"),
    stats= c(
    min = min(v, na.rm = TRUE),
    max = max(v, na.rm = TRUE),
    mediana = median(v, na.rm = TRUE),
    media = media,
    sd = std,
    IQR = IQR(v, na.rm = TRUE),
    cv = coef_var
    ))
  # imposto rownames = NULL per rimuovere i nomi degli indici e reimpostarli
    rownames(var_stats) <- NULL
  return(var_stats)
}

# definisco una funziona per automatizzare il calcolo degli indici di forma
indici_f <- function(variabile){
  variabile <- na.omit(variabile)
  N <- length(variabile)
  media <- mean(variabile)
  std <- sd(variabile)
  mu3 <- sum((variabile - media)^3)/N
  mu4 <- sum((variabile - media)^4)/N 
  # creo il df per mostrare le stats in modo ordinato
  ind_f_stats = data.frame(
    etichette = c(
    "Fisher", "curtosi"),
    stats= c(
    fisher = mu3/std^3,
    curtosi = (mu4/std^4) - 3)
    )
  # imposto rownames = NULL per rimuovere i nomi degli indici e reimpostarli
    rownames(ind_f_stats) <- NULL
  return(ind_f_stats)
}


# definisco una funzione per visualizzare le curve di densità e distribuzione
dens_viz <- function(variabile, x){
  variabile <- na.omit(variabile)
  dens <- density(variabile)
  hist(variabile, probability = TRUE, main = paste("Distribuzione di", x), xlab = x, col = 'white', yaxt = "n")
  lines(dens, col = "red", lwd = 2)
  
  lines(seq(min(variabile), max(variabile), length.out = 100),
        dnorm(seq(min(variabile), max(variabile), length.out = 100),
              mean = mean(variabile),
              sd = sd(variabile)),
        col = "darkgreen", lwd = 2)
  # utilizzo questi metodi per rendere l'asse delle y più leggibile,
  # senza notazioni scientifiche.
  # i valori esprimono la densità di probabilità, 
  # cioè una misura di quanto i dati sono concentrati in un certo intervallo.
  y_ticks <- pretty(dens$y)
  y_labels <- format(y_ticks, scientific = FALSE)
  axis(2, at = y_ticks, labels = y_labels)
}

# calcolo le stats per le colonne con variabili quantitative:
# sales:
sales_iv <- eda(re_texas$sales)$stats
# calcolo indici di forma:
sales_if <- indici_f(re_texas$sales)$stats

# volume:
vol_iv <- eda(re_texas$volume)$stats
# calcolo indici di forma
vol_if <- indici_f(re_texas$volume)$stats

# median_price
mp_iv <- eda(re_texas$median_price)$stats
# calcolo indici di forma
mp_if <- indici_f(re_texas$median_price)$stats

# listings:
listings_iv <- eda(re_texas$listings)$stats
# calcolo indici di forma
listings_if <- indici_f(re_texas$listings)$stats

# months_inventory:
mi_iv <- eda(re_texas$months_inventory)$stats
# calcolo indici di forma
mi_if <- indici_f(re_texas$months_inventory)$stats


retexas_var_stats <- data.frame(etichette = c(
    "Min", "Max", "Mediana", "Media", "SD", "IQR", "CV"),
    sales = sales_iv,
    volume = vol_iv,
    median_price = mp_iv,
    listings = listings_iv,
    months_inventory = mi_iv)

knitr::kable(retexas_var_stats, row.names = FALSE)
etichette sales volume median_price listings months_inventory
Min 79.00000 8.16600 73800.00000 743.00000 3.400000
Max 423.00000 83.54700 180000.00000 3296.00000 14.900000
Mediana 175.50000 27.06250 134500.00000 1618.50000 8.950000
Media 192.29167 31.00519 132665.41667 1738.02083 9.192500
SD 79.65111 16.65145 22662.14869 752.70776 2.303669
IQR 120.00000 23.23350 32750.00000 1029.50000 3.150000
CV 41.42203 53.70536 17.08218 43.30833 25.060306
retexas_id_f_stats <- data.frame(
  etichette = c(
    "Fisher", "curtosi"),
    sales = sales_if,
    volume = vol_if,
    median_price = mp_if,
    listings = listings_if,
    months_inventory = mi_if)

knitr::kable(retexas_id_f_stats, row.names = FALSE)
etichette sales volume median_price listings months_inventory
Fisher 0.7136206 0.8792182 -0.3622768 0.6454431 0.0407194
curtosi -0.3355200 0.1505673 -0.6427292 -0.8101534 -0.1979448
# visuzalizzo i grafici per le varibaili 
dens_viz(re_texas$sales, 'Sales')

dens_viz(re_texas$volume, 'Volume')

dens_viz(re_texas$median_price, 'Median Price')

dens_viz(re_texas$listings, 'Listings')

dens_viz(re_texas$months_inventory, 'Months Inventory')

ANALISI FINALE

Le variabili Sales e Volume presentano una lieve asimmetria positiva, confermata sia dall’indice di asimmetria sia dal grafico di densità. La curva risulta abbastanza vicina alla normale teorica nella parte centrale, ma mostra una coda destra più pronunciata. Nel complesso, le due variabili non evidenziano forti deviazioni dalla campana normale, pur non risultando perfettamente gaussiane. Median Price mostra invece una lieve asimmetria negativa, mentre Months Inventory appare quasi simmetrica e complessivamente vicina a una distribuzione normale. La variabile Listings, infine, presenta una distribuzione meno regolare e apparentemente multimodale, con più concentrazioni di valori e un andamento che si discosta in modo più evidente da una singola curva gaussiana.

3. IDENTIFICAZIONI DELLE VARIABILI CON MAGGIORE VARIABILITÀ E ASIMMETRIA

VARIABILIÀ:

Le variabili sono misurate su scale differenti e i coefficienti di variazione risultano diversi tra loro. Questo indica che la variabilità relativa non è omogenea, ma differisce sensibilmente da una variabile all’altra. Ad esempio, volume mostra una dispersione relativa più elevata, mentre median_price ne presenta una minore: il CV passa da ~17 a ~54, volume risulta molto meno omogenea in termini di variabilità relativa rispetto a median_price. Questo aspetto va considerato nell’interpretazione complessiva dei dati, perché può incidere sulla robustezza delle conclusioni e sulla lettura comparata delle variabili.

ASIMMETRIA:

In termini assoluti, la variabile che presenta l’asimmetria maggiore si conferma essere ‘volume’, con un indice di Fisher pari a ~0.88 presenta una asimmetria positiva. Al contrario, la variabile che si avvicina a una forma simmetrica è ‘months_inventory’: con un indice di Fisher pari a 0.04, mostra una distribuzione quasi simile a una normale. La variabile ‘median_price’ (-0.36) mostra invece una lieve asimmetria negativa.

FORMA:

Analizzando l’indice di curtosi in eccesso, emerge che quasi tutte le variabili quantitative presentano valori negativi ma estremamente prossimi allo zero (in particolare ‘months_inventory’ con ~-0.20 e ‘volume’ con ~0.15. Questo indica che queste distribuzioni sono quasi perfettamente mesocurtiche, ovvero molto vicine alla forma della normale teorica. L’unica eccezione è rappresentata dalla variabile ‘listings’, che registra una curtosi pari a ~-0.81: la campana risulta più schiacciata.

4. SUDDIVIONE IN CLASSI DI UNA VARIABILE QUANTITATIVA

# suddivido in classi le osservazioni della variabile 'sales'
re_texas$sales_class <- cut(re_texas$sales, breaks = c(49, 99, 149, 199, 249, 299, 349, 399, 449))

# mostro la tabella di frequenza per le classi della variabile 'sales_class'
kable(tab_freq(re_texas, re_texas$sales_class, 'Sales Class'), row.names = FALSE)
Sales Class freq_ass freq_rel freq_pct
(49,99] 20 0.0833 8.33
(99,149] 69 0.2875 28.75
(149,199] 58 0.2417 24.17
(199,249] 33 0.1375 13.75
(249,299] 34 0.1417 14.17
(299,349] 14 0.0583 5.83
(349,399] 9 0.0375 3.75
(399,449] 3 0.0125 1.25
# mostro il grafico
ggplot(re_texas)+
  geom_bar(aes(x=sales_class),
            fill = 'red',
            colour = 'black')+
  labs(title=paste('Frequenze Assolute di Sales Class'))+
  theme_classic()

Interpretazione del grafico

Il grafico presenta una distribuzione asimmetrica positiva, con la maggior parte dei valori concentrati tra 99 e 199. Questo risultato indica che i numeri di vendite concluse si concentrano pricipalmente in quell’intervallo. Possiamo notare un secondo gruppo di vendite concentrato nell’intervallo 199 e 249. Questa evidenza grafica è coerente con un indice di asimmetria positivo: conferma i valori più alti per le classi iniziali; valori che vanno diminuendo man mano che le classi aumenetano, più alto è il valore, meno frequente è la vendita.

# calcolo l'indice di eterogeneità di Gini creando una funzione 
gini.index <- function(col_nome){
  ni<- table(col_nome)
  fi<- ni/length(col_nome)
  fi2<- fi^2
  J<- length(table(col_nome))
  # avendo tutti gli elementi calcolo gini
  gini<- 1-sum(fi2)
  gini.normalizzato<- round(gini/((J-1)/J), 2)
  cat("Il valore dell'indice di Gini normalizzato è pari a:", gini.normalizzato)
}

gini.index(re_texas$sales_class)
## Il valore dell'indice di Gini normalizzato è pari a: 0.92

Il valore dell’indice di Gini è pari a 0.92, posso dunque concludere che i valori sono moderatamnte eterogenei

5. CALCOLO DELLA PROBABILITÀ

# Per il totale di righe uso già l'oggetto N
 N <- dim(re_texas)[1]

# calcolo la probabilità che da un estrazione esca Beaumont 
prob_beaumont <- sum(re_texas$city == "Beaumont")/N
# La probabilità è di 0.25, c'è quindi il 25% di possibilità che venga estratta

# calcolo la probabilità che da un estrazione esca Luglio
prob_luglio <- round(sum(re_texas$month == 7)/N, 2)
# La probabilità è di ~0.08, c'è quindi circa l'8% di possibiltà che venga estratta

# calcolo la probabilità che da un estrazione esca Dicembre e 2012
prob_anno_mese <- round(sum(re_texas$month == 12 & re_texas$year == 2012)/N, 2)
# La probabilità è di 0.02, c'è quindi circa il 2% di possibiltà che venga estratta

6. CREAZIONE NUOVE VARIABILI

6.1 calcolo il prezzo medio: metto in rapporto le variabili volume e sales

re_texas$prezzo_medio_mln <- round((re_texas$volume*1000000)/re_texas$sales, 3)
# controllo il range
range(re_texas$prezzo_medio_mln)
## [1]  97010.2 213233.9

Il prezzo medio varia da un minimo di ~97 e un massimo di~213 mila dollari. Considerando che il prezzo mediano si muove in un range tra ~73 e 180 mila posso concludere che all’interno della variabile sales sono presenti immobili con valori alti, degli outliers che di conseguenza spingono il alto in prezzo medio. Per questo è più corretto per Texas Reality Insight considerare il prezzo mediano, in quanto più rappresentativo e robusto rispetto a al prezzo medio.

6.2 misuro l’efficacia degli annunci

re_texas$sales_eff <- round(re_texas$sales/re_texas$listings, 2)

# creo uno scatter mettendo in relazione months_inventory e sales_eff
ggplot(data=re_texas) + 
  geom_point(aes(x=re_texas$sales_eff,
                 y=re_texas$months_inventory), 
             colour = 'red')+
  labs(x='Indice di efficienza',
       y='Mesi Necessari',
       title='Rapporto tra efficienza delle vendite e tempo per chiudere la vendita')+
  theme_bw()

Lo scatter plot mostra che: osservazioni con livelli simili di efficienza possono presentare valori molto diversi di months inventory. Questo suggerisce che l’efficienza non dipende esclusivamente dalla velocità di assorbimento del mercato, ma anche da altri fattori. In generale, valori più elevati di months inventory sono associati a un mercato più lento e/o a una minore capacità di smaltire rapidamente le inserzioni.

# creo un boxplot mettendo in relazione l'efficienza nei singoli mesi di riferimento
ggplot(data=re_texas) + 
  geom_boxplot(aes(x=factor(month), 
                   y=sales_eff),
               colour = 'red')+
  labs(x='Mese',
       y='Indice di efficienza',
       title='Rapporto tra mese di riferimento ed efficienza delle vendite')+
  theme_bw()

Il boxplot suggerisce che: l’indice di efficienza tende a essere leggermente più elevato nei mesi centrali dell’anno. In particolare, tra primavera ed estate si osservano mediane superiori rispetto ai mesi iniziali e finali. La presenza di alcuni outlier nei mesi centrali indica inoltre una maggiore variabilità delle osservazioni in questi periodi.

7. USARE IL PACCHETTO DPLYR

# definisco una funzione per automatizzare l'EDA
eda_2 <- function(data, g_col, var_col){
  data |>
    group_by({{g_col}}) |>
    summarise(media=mean({{var_col}}),
              std=sd({{var_col}}))
}

# creo l'istanza chiamando la funzione creata: raggruppo per city e sales
city_sales <- eda_2(re_texas, city, volume)
kable(city_sales, row.names = FALSE)
city media std
Beaumont 26.13160 6.970384
Bryan-College Station 38.19160 17.248577
Tyler 45.76738 13.107146
Wichita Falls 13.93017 3.239766
# creo l'istanza chiamando la funzione creata: raggruppo per year e volume
year_volume <- eda_2(re_texas, year, volume)
kable(year_volume, row.names = FALSE)
year media std
2010 25.67590 10.79510
2011 25.15781 12.20349
2012 29.26756 14.52269
2013 35.15240 17.93470
2014 39.77227 21.18628
# creo l'istanza chiamando la funzione creata: raggruppo per city e sales
month_list <- eda_2(re_texas, month, listings)
kable(month_list,row.names = FALSE)
month media std
1 1647.05 704.6140
2 1692.50 711.2004
3 1756.70 727.3546
4 1825.70 770.4287
5 1823.85 790.2234
6 1833.25 811.6288
7 1821.20 826.7196
8 1786.30 815.8664
9 1748.90 802.6563
10 1710.35 779.1649
11 1652.70 741.2533
12 1557.75 692.5678
# rappresento graficamente i risultati, creo una funzione per un grafico a linea
line_viz <- function(dati, col_1, col_2){
  ggplot(data = dati)+
    geom_line(aes(x = {{col_1}}, y = {{col_2}}))+
    theme_bw()
}

# visualizzo un grafico a barre per la combinazione città/vendite
ggplot(data = city_sales)+
  geom_bar(aes(x = city, y = media),
            stat = "identity",
            fill ='red')+
  theme_bw()

# visualizzo i trend temporali di 'year' e 'month' con un grafico a linee
line_viz(year_volume, year, media)

line_viz(month_list, month, media)

8. CREAZIONE DI VISUALIZZAZIONI CON GGPLOT

# BOXPLOT: distribuzione del prezzo mediano tra le città
ggplot(data = re_texas)+
  geom_boxplot(aes(x = as.factor(year), y = median_price, fill = city))+
  labs(title = "Distribuzione del prezzo mediano tra le città",
       x = 'Anno',
       y = 'Prezzo Mediano',
       fill='Città',
       )+
  theme_bw()+
  theme(
  plot.title = element_text(size = 35),
  axis.title = element_text(size = 30),
  axis.text = element_text(size = 30),
  legend.title = element_text(size = 25),
  legend.text = element_text(size=20)
)

# BARPLOT: confronto il totale delle vendite raggruppate per mese e città
month_city_volume <- re_texas |>
                        summarise(.by = c(city, month, year),
                                  tot_volume = sum(volume))
  
# visualizzo il grafico a barre
ggplot(data = month_city_volume)+
  geom_col(aes(x = month, y = tot_volume,
           fill = city),
           position = position_dodge())+
  facet_wrap(~year)+
  labs(title = "Totale delle vendite raggruppate per mese e città",
       x = 'Mesi',
       y = 'Totale volume')+
  scale_x_continuous(breaks = seq(1,12,1))+
  theme_bw()+
  theme(axis.text.x = element_text(size = 4))

# LINECHART: confronto il totale delle vendite per anni differenti

year_tot_sales <- re_texas |>
                    summarise(.by = c(year, month),
                              tot_volume = sum(volume))
year_tot_sales$date <- as.Date(paste(year_tot_sales$year, year_tot_sales$month, "01", sep = "-"))

# visualizzo il grafico a linee, una linea per anno di vendite raggruppate
ggplot(data = year_tot_sales)+
  geom_line(aes(x = date, y = tot_volume, color = as.factor(year)))+
  labs(title = "Andamento dei volumi delle vendite per anno",
       x = 'Anni',
       y = 'Totale volume',
       color = 'Anno')+
  scale_x_date(breaks = seq(as.Date("2010-01-01"), as.Date("2015-01-01"), by = "year"))+
theme_bw()

9. CONCLUSIONI

L’analisi descrittiva del dataset evidenzia un mercato caratterizzato da una stagionalità marcata, durante i mesi estivi si registrano picchi nelle vendite concluse e nel volume generato

Dal punto di vista della variabilità, le variabili quantitative presentano CV diversi e dunque una variabilità relativa eterogenea: il prezzo mediano mostra una forte stabilità sul territorio (CV più contenuto), mentre i volumi complessivi di vendita presentano oscillazioni maggiori nel tempo. Per qunato riguarda la forma presentano invece una leggera asimmetria, sia positiva che negativa, a seconda della variabile esaminata.

Le nuove variabili costruite suggeriscono, con forza diversa, trend temporali simili. Mettono in evidenza eterogeneità territoriale e differenze di efficienza: in particolare, l’efficienza delle vendite e il prezzo medio suggeriscono differenze nella capacità di assorbimento del mercato e nella presenza di valori estremi. I valori del prezzo, inoltre, differiscono in relazione all’area geografica; evidenziato dalla distribuzione dei valori nel grafico del boxplot

Nel complesso, i risultati indicano un contesto eterogeneo ma interpretabile, in cui la lettura congiunta di distribuzioni, indici sintetici e grafici permette di individuare con chiarezza le principali tendenze del mercato.