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:
• Identificare e interpretare i trend storici delle vendite immobiliari
in Texas.
• Valutare l’efficacia delle strategie di marketing delle
inserzioni immobiliari.
• Offrire una rappresentazione grafica dei
dati che evidenzi la distribuzione dei prezzi e delle vendite tra città,
mesi e anni.
L’analisi statistica proposta permetterà a Texas Realty Insights di
ottimizzare le loro strategie di mercato, identificando città con
opportunità di crescita e valutando l’efficacia delle inserzioni
immobiliari nel tempo.
Grazie a una visione chiara e strutturata dei
dati, l’azienda potrà prendere decisioni basate su informazioni
concrete, migliorando la gestione delle vendite immobiliari in
Texas.
Dataset: “Real Estate Texas.csv”
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
dataset <- read.csv("/home/denis/Desktop/realestate_texas.csv",sep=",")
str(dataset)
## '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 ...
dim(dataset)
## [1] 240 8
head(dataset)
## city year month sales volume median_price listings months_inventory
## 1 Beaumont 2010 1 83 14.162 163800 1533 9.5
## 2 Beaumont 2010 2 108 17.690 138200 1586 10.0
## 3 Beaumont 2010 3 182 28.701 122400 1689 10.6
## 4 Beaumont 2010 4 200 26.819 123200 1708 10.6
## 5 Beaumont 2010 5 202 28.833 123100 1771 10.9
## 6 Beaumont 2010 6 189 27.219 122800 1803 11.1
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(ggplot2)
library(moments)
attach(dataset)
city: nominal qualitative variable —-> groups
comparison (location differences…)
year: discrete
quantitative variable representing the temporal dimension —->
temporal analysis (historical trends, market cycles…)
month: cyclic temporal ordinal variable —-> temporal
analysis (seasonality and recurring patterns during the year)
sales: discrete quantitative variable —->
descriptive statistics in relation to temporal and spatial
comparison
volume: continuous quantitative variable
—-> descriptive statistics in relation to temporal and spatial
comparison
median_price: continuous quantitative
variable —-> descriptive statistics in relation to temporal and
spatial comparison
listings: discrete quantitative
variable —-> descriptive statistics in relation to temporal and
spatial comparison
months_inventory: continuous
quantitative variable —-> selling velocity indicator (information on
market availability and request)
summary(dataset[c("sales","volume","median_price","listings","months_inventory")])
## sales volume median_price listings
## Min. : 79.0 Min. : 8.166 Min. : 73800 Min. : 743
## 1st Qu.:127.0 1st Qu.:17.660 1st Qu.:117300 1st Qu.:1026
## Median :175.5 Median :27.062 Median :134500 Median :1618
## Mean :192.3 Mean :31.005 Mean :132665 Mean :1738
## 3rd Qu.:247.0 3rd Qu.:40.893 3rd Qu.:150050 3rd Qu.:2056
## Max. :423.0 Max. :83.547 Max. :180000 Max. :3296
## months_inventory
## Min. : 3.400
## 1st Qu.: 7.800
## Median : 8.950
## Mean : 9.193
## 3rd Qu.:10.950
## Max. :14.900
mean_vector <- sapply(dataset[c("sales","volume","median_price","listings","months_inventory")],mean)
mean_vector
## sales volume median_price listings
## 192.29167 31.00519 132665.41667 1738.02083
## months_inventory
## 9.19250
std_dev_vector <- sapply(dataset[c("sales","volume","median_price","listings","months_inventory")],sd)
std_dev_vector
## sales volume median_price listings
## 79.651111 16.651447 22662.148687 752.707756
## months_inventory
## 2.303669
cv_vector <- std_dev_vector/mean_vector*100
cv_vector
## sales volume median_price listings
## 41.42203 53.70536 17.08218 43.30833
## months_inventory
## 25.06031
skewness_vector <- sapply(dataset[c("sales","volume","median_price","listings","months_inventory")],skewness)
skewness_vector
## sales volume median_price listings
## 0.71810402 0.88474203 -0.36455288 0.64949823
## months_inventory
## 0.04097527
kurtosis_vector <- sapply(dataset[c("sales","volume","median_price","listings","months_inventory")],kurtosis)-3
kurtosis_vector
## sales volume median_price listings
## -0.3131764 0.1769870 -0.6229618 -0.7917900
## months_inventory
## -0.1744475
Sales: great variability, slightly positive skewness
(righteous asymmetry) and negative kurtosis
Volume:
greatest variability, positive skewness (righteous asymmetry) and
positive kurtosis
Median_price: little variability,
slightly negative skewness (almost symmetric) and negative kurtosis
Listings: great variability, slightly positive skewness
(righteous asymmetry) and negative kurtosis
Months_inventory: great variability, slightly positive
skewness (practically symmetric) and negative kurtosis
In order to determine the variable with greater variability, it is
necessary to compare the coefficients of variation.
The variable
with the greatest variability is “volume”.
In order to determine the
variable with the greatest skewness, it is necessary to compare the
skewness indexes.
The variable with the greatest skewness is
“volume”.
It is possible to observe that “volume” shows a
distribution with great variability and, therefore, a large dispersion
around the mean value.
The positive skewness indicates that most
observations are concentrated at relatively low values, while a smaller
number of observations with very high values produce a long right
tail.
sales_cl <- cut(sales,breaks=seq(70,430,10),right=TRUE,include.lowest=TRUE)
n <- dim(dataset)[1]
dist_freq_sales <- as.data.frame(cbind(ni=table(sales_cl),
fi=table(sales_cl)/n,
Ni=cumsum(table(sales_cl)),
Fi=cumsum(table(sales_cl))/n))
dist_freq_sales$classes <- rownames(dist_freq_sales)
dist_freq_sales$classes <- factor(dist_freq_sales$classes, levels=unique(dist_freq_sales$classes))
moda_sales_cl <- names(table(sales_cl)) [table(sales_cl)==max(table(sales_cl))]
ggplot(data=dist_freq_sales)+
geom_col(aes(x=classes,y=ni),
col="black",
fill="red")+
labs(title="Frequency Distribution of Sales divided in Classes",
x="sales [classes]",
y="frequency")+
scale_y_continuous(breaks=seq(0,20,5))+
theme_dark(base_size = 15)+
theme(axis.text.x=element_text(size=10,angle=80, hjust=1))
Gini Index is usually applied to nominal qualitative variables. It is
possible to apply it to a quantitative variable divided in classes, but
it’s not the best option.
gini_index <- function(x){
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(gini.norm)
}
gini_index(sales_cl)
## [1] 0.9846786
The normalized Gini index is very close to one, having a value of
0.98. It indicates a highly heterogeneous distribution.
No single
class dominates the frequency distribution.
x <- table(city)
pBeaumont <- x["Beaumont"]/n
y <- table(month)
pLuglio <- y["7"]/n
z <- table(month,year)
pDicembre12 <- z["12","2012"]/n
pBeaumont
## Beaumont
## 0.25
pLuglio
## 7
## 0.08333333
pDicembre12
## [1] 0.01666667
Randomly selecting one observation from the data-set, the probability
to extract the city Beaumont is 0.25.
The probability to extract
July is about 0.083 and the probability to extract December 2012 is
approximately 0.017.
dataset["average_price"] <- volume*(10^6)/sales
df_avg_price_cy <- dataset %>%
group_by(city,year) %>%
summarise(average_avg_price_cy = mean(average_price),
std_dev_avg_price_cy = sd(average_price))
## `summarise()` has regrouped the output.
## ℹ Summaries were computed grouped by city and year.
## ℹ Output is grouped by city.
## ℹ Use `summarise(.groups = "drop_last")` to silence this message.
## ℹ Use `summarise(.by = c(city, year))` for per-operation grouping
## (`?dplyr::dplyr_by`) instead.
dataset["listing_efficiency"] <- sales/listings
df_efficiency_cy <- dataset %>%
group_by(city,year) %>%
summarise(average_efficiency_cy = mean(listing_efficiency),
std_dev_efficiency_cy = sd(listing_efficiency))
## `summarise()` has regrouped the output.
## ℹ Summaries were computed grouped by city and year.
## ℹ Output is grouped by city.
## ℹ Use `summarise(.groups = "drop_last")` to silence this message.
## ℹ Use `summarise(.by = c(city, year))` for per-operation grouping
## (`?dplyr::dplyr_by`) instead.
ggplot(data=df_efficiency_cy,aes(x=year,y=average_efficiency_cy,color=city))+
geom_line()+
geom_point(size=3)+
labs(title="Average Efficiency by Year and City",
x="Year",
y="Average Efficiency")+
scale_y_continuous(breaks=seq(0.003,0.01,0.0005))+
theme_minimal()+
theme(plot.title = element_text(hjust=.5))
ggplot(dataset, aes(x=months_inventory,y=listing_efficiency,color=city))+
geom_point(size=3)+
labs(title="Relation between Months Inventory and Listing Efficiency",
x="Months Inventory",
y="Listing Efficiency")+
theme_minimal()+
theme(plot.title = element_text(hjust=.5))
Unlike median_price, which represents the median transaction value,
average_price is the arithmetic mean of the selling prices and may be
more influenced by unusually expensive properties.
The efficiency
indicator measures the proportion of active listings that resulted in
actual sales..
Higher values indicate that a larger fraction of
listed properties were successfully sold, suggesting stronger market
demand, more effective marketing strategies or more convenient and
attractive pricing.
Comparing cities over time reveals differences
in market performance.
Cities with consistently high efficiency
values are characterized by a faster turnover of listed properties,
whereas lower efficiency values suggest that properties remain on the
market longer or that demand is weaker.
Temporal changes in
efficiency may also reflect broader economic conditions.
An
increasing trend indicates improving market dynamics, while a decreasing
trend may lead to slower sales or an excess of offer.
A higher
efficiency is generally expected to correspond to a lower value of
months_inventory, since a larger proportion of active listings is
converted into sales in a shorter period of time.
As shown in the
scatter plot, it is possible to suppose a negative association between
listing_efficiency and months_inventory.
df_volume_c <- dataset %>%
group_by(city) %>%
summarise(average_volume_c = mean(volume),
std_dev_volume_c = sd(volume),
min_volume_C = min(volume),
max_volume_c = max(volume))
df_volume_c
## # A tibble: 4 × 5
## city average_volume_c std_dev_volume_c min_volume_C max_volume_c
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 Beaumont 26.1 6.97 13.5 42.0
## 2 Bryan-College Sta… 38.2 17.2 15.2 83.5
## 3 Tyler 45.8 13.1 21.0 80.8
## 4 Wichita Falls 13.9 3.24 8.17 20.9
df_sales_c <- dataset %>%
group_by(city) %>%
summarise(average_sales_c = mean(sales),
std_dev_sales_c = sd(sales),
min_sales_C = min(sales),
max_sales_c = max(sales))
df_sales_c
## # A tibble: 4 × 5
## city average_sales_c std_dev_sales_c min_sales_C max_sales_c
## <chr> <dbl> <dbl> <int> <int>
## 1 Beaumont 177. 41.5 83 273
## 2 Bryan-College Station 206. 85.0 89 403
## 3 Tyler 270. 62.0 143 423
## 4 Wichita Falls 116. 22.2 79 167
df_volume_ym <- dataset %>%
group_by(year,month) %>%
summarise(average_volume_ym = mean(volume),
std_dev_volume_ym = sd(volume),
min_volume_ym = min(volume),
max_volume_ym = max(volume))
## `summarise()` has regrouped the output.
## ℹ Summaries were computed grouped by year and month.
## ℹ Output is grouped by year.
## ℹ Use `summarise(.groups = "drop_last")` to silence this message.
## ℹ Use `summarise(.by = c(year, month))` for per-operation grouping
## (`?dplyr::dplyr_by`) instead.
df_volume_ym
## # A tibble: 60 × 6
## # Groups: year [5]
## year month average_volume_ym std_dev_volume_ym min_volume_ym max_volume_ym
## <int> <int> <dbl> <dbl> <dbl> <dbl>
## 1 2010 1 15.9 6.92 8.95 25.5
## 2 2010 2 19.2 8.54 9.38 30.1
## 3 2010 3 28.0 7.30 18.2 35.9
## 4 2010 4 33.8 13.3 19.8 49.9
## 5 2010 5 35.9 13.2 20.9 48.4
## 6 2010 6 34.5 13.6 19.2 47.5
## 7 2010 7 26.7 12.1 12.4 40.9
## 8 2010 8 28.6 10.7 15.3 39.7
## 9 2010 9 22.2 7.23 16.5 32.1
## 10 2010 10 21.8 8.07 13.6 32.1
## # ℹ 50 more rows
df_sales_ym <- dataset %>%
group_by(year,month) %>%
summarise(average_sales_ym = mean(sales),
std_dev_sales_ym = sd(sales),
min_sales_ym = min(sales),
max_sales_ym = max(sales))
## `summarise()` has regrouped the output.
## ℹ Summaries were computed grouped by year and month.
## ℹ Output is grouped by year.
## ℹ Use `summarise(.groups = "drop_last")` to silence this message.
## ℹ Use `summarise(.by = c(year, month))` for per-operation grouping
## (`?dplyr::dplyr_by`) instead.
df_sales_ym
## # A tibble: 60 × 6
## # Groups: year [5]
## year month average_sales_ym std_dev_sales_ym min_sales_ym max_sales_ym
## <int> <int> <dbl> <dbl> <int> <int>
## 1 2010 1 105. 36.6 83 160
## 2 2010 2 122. 40.3 91 181
## 3 2010 3 189. 43.6 147 250
## 4 2010 4 229 64.0 167 316
## 5 2010 5 233. 58.8 165 282
## 6 2010 6 216. 71.4 129 286
## 7 2010 7 178 62.5 104 255
## 8 2010 8 184. 45 130 238
## 9 2010 9 150. 47.2 122 220
## 10 2010 10 141. 45.7 100 202
## # ℹ 50 more rows
df_volume_cy <- dataset %>%
group_by(city,year) %>%
summarise(average_volume_cy = mean(volume),
std_dev_volume_cy = sd(volume),
min_volume_cy = min(volume),
max_volume_cy = max(volume))
## `summarise()` has regrouped the output.
## ℹ Summaries were computed grouped by city and year.
## ℹ Output is grouped by city.
## ℹ Use `summarise(.groups = "drop_last")` to silence this message.
## ℹ Use `summarise(.by = c(city, year))` for per-operation grouping
## (`?dplyr::dplyr_by`) instead.
df_volume_cy
## # A tibble: 20 × 6
## # Groups: city [4]
## city year average_volume_cy std_dev_volume_cy min_volume_cy max_volume_cy
## <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 Beaumo… 2010 22.7 4.95 14.2 28.8
## 2 Beaumo… 2011 21.1 4.30 15.5 28.5
## 3 Beaumo… 2012 24.5 4.92 13.5 31.1
## 4 Beaumo… 2013 30.3 6.44 20.3 42.0
## 5 Beaumo… 2014 32.1 7.05 18.1 41.2
## 6 Bryan-… 2010 28.7 10.8 15.2 47.5
## 7 Bryan-… 2011 28.9 10.3 15.2 47.8
## 8 Bryan-… 2012 35.4 13.5 19.8 55.4
## 9 Bryan-… 2013 45.1 19.5 19.0 76.1
## 10 Bryan-… 2014 52.8 18.0 29.5 83.5
## 11 Tyler 2010 36.3 8.39 24.4 49.9
## 12 Tyler 2011 38.6 9.41 21.0 52.3
## 13 Tyler 2012 44.0 10.2 25.4 57.4
## 14 Tyler 2013 50.3 10.3 32.1 63.0
## 15 Tyler 2014 59.6 12.8 36.9 80.8
## 16 Wichit… 2010 15.0 4.07 8.95 20.9
## 17 Wichit… 2011 12.1 2.52 8.17 15.3
## 18 Wichit… 2012 13.2 2.66 9.70 17.8
## 19 Wichit… 2013 14.9 3.11 9.67 19.1
## 20 Wichit… 2014 14.5 3.13 9.63 18.7
ggplot(df_volume_cy, aes(x=year,y=average_volume_cy,color=city))+
geom_line()+
geom_point(size=3)+
labs(title="Average Volume by Year and City",
x="Year",
y="Average Volume")+
scale_y_continuous(breaks=seq(10,65,5))
df_sales_cy <- dataset %>%
group_by(city,year) %>%
summarise(average_sales_cy = mean(sales),
std_dev_sales_cy = sd(sales),
min_sales_cy = min(sales),
max_sales_cy = max(sales))
## `summarise()` has regrouped the output.
## ℹ Summaries were computed grouped by city and year.
## ℹ Output is grouped by city.
## ℹ Use `summarise(.groups = "drop_last")` to silence this message.
## ℹ Use `summarise(.by = c(city, year))` for per-operation grouping
## (`?dplyr::dplyr_by`) instead.
ggplot(df_sales_cy, aes(x=year,y=average_sales_cy,color=city))+
geom_line()+
geom_point(size=3)+
labs(title="Average Sales by Year and City",
x="Year",
y="Average Sales")+
scale_y_continuous(breaks=seq(100,400,25))
ggplot(data=dataset)+
geom_boxplot(aes(x=city, y=median_price, fill=city))+
labs(title="Boxplot of Median Price by City",
x="City",
y="Median Price")+
theme_bw(base_size = 18)+
theme(legend.position = "none")
df_sales_cm <- dataset %>%
group_by(city,month) %>%
summarise(average_sales_cm = mean(sales),
std_dev_sales_cm = sd(sales),
min_sales_cm = min(sales),
max_sales_cm = max(sales))
## `summarise()` has regrouped the output.
## ℹ Summaries were computed grouped by city and month.
## ℹ Output is grouped by city.
## ℹ Use `summarise(.groups = "drop_last")` to silence this message.
## ℹ Use `summarise(.by = c(city, month))` for per-operation grouping
## (`?dplyr::dplyr_by`) instead.
ggplot(df_sales_cm, aes(x=factor(month),y=average_sales_cm,fill=city))+
geom_col(color="black")+
labs(title="Average Sales by Month and City",
x="Month",
y="Average Sales")+
geom_text(aes(label=average_sales_cm), position=
position_stack(vjust=0.5),color="black",size=3)+
scale_y_continuous(breaks=seq(0,1000,100))
ggplot(dataset, aes(x=factor(month),y=sales,fill=city))+
geom_col(color="black")+
facet_wrap(factor(year))+
labs(title="Sales by Month and City",
x="Months",
y="Sales")+
theme_minimal()
dataset$data <- as.Date(paste(year,month,"01", sep="-"))
ggplot(dataset, aes(x=data,y=sales,col=city))+
geom_line(linewidth=1)+
geom_point(size=2)+
scale_x_date(date_labels ="%b %Y",date_breaks = "4 months")+
labs(title="Sales Trend per City",
x="Time (Year and Month)",
y="Sales")+
theme_minimal()+
theme(axis.text.x = element_text(angle=45, hjust=1))
ggplot(dataset, aes(x=data,y=volume,col=city))+
geom_line(linewidth=1)+
geom_point(size=2)+
scale_x_date(date_labels ="%b %Y",date_breaks = "4 months")+
labs(title="Volume Trend per City",
x="Time (Year and Month)",
y="Volume")+
theme_minimal()+
theme(axis.text.x = element_text(angle=45, hjust=1))
ggplot(dataset,aes(x=factor(month),
y=sales,
fill=city))+
geom_col(position="fill")+
facet_wrap(~year)+
labs(y="Proportion")+
labs(title="Normalized Sales per Month and Year",
x="Month",
y="Normalized Sales")+
theme_grey()
The analysis of the Texas real estate market shows significant
differences in sales activity, transaction volume and property prices
across cities and over time.
Descriptive statistics reveal that
variables such as total sales value and number of listings present high
variability, suggesting that the real estate market is characterized by
heterogeneous conditions depending on geographic location and historical
period.
To wrap up, this specific market is not uniform. Each city
is characterized by its own dynamics and there is a high variability
across location and over time.
The boxplot analysis of median prices highlights relevant differences
among cities.
Some locations show higher median values, suggesting
stronger housing demand or a more expensive real estate market.
The
city with the highest median price values is Bryan_College Station.
This suggests that Bryan_College Station represents a higher-value real
estate market compared with the other cities analyzed.
This
characteristic may make the city attractive for investors, although
further analysis would be required.
Other cities present lower
prices, suggesting more homogeneous and accessible markets.
The city
with the lowest values is Wichita Falls, suggesting a more affordable
housing market.
The presence of outliers indicates that these
observations may represent luxury properties or exceptional transactions
and should be investigated.
The temporal analysis shows that sales and total transaction volume
changed throughout the years, suggesting the influence of economic
cycles and changes in market conditions.
The monthly analysis
suggests the presence of seasonal patterns in real estate activity.
Some months show higher sales levels compared with others, indicating
that the housing market may be affected by seasonal factors such as
buyer behavior, economic conditions and timing of property transactions.
Some cities maintained relatively stable trends, like Wichita
Falls, while others experienced stronger fluctuations, indicating
different levels of market sensitivity, like Tyler.
An increasing
trend in sales suggests an expansion of market activity, while periods
of decline may indicate slower demand or reduced purchasing
activity.
The efficiency indicator provides information about how effectively
active listings are converted into sales.
Cities with higher
efficiency values appear to have a stronger ability to transform
available properties into completed transactions, suggesting higher
demand or more effective pricing strategies.
This could be the case
of Bryan-College Station, showing an improving efficiency along the
years.
On the other hand, lower efficiency values may indicate a
slower market, excessive supply or the need for improved listing
strategies.
That’s the case of Wichita Falls, showing an almost
steady efficiency along the years.
The analysis suggests that Texas Realty Insights should adopt a
differentiated strategy based on city-specific characteristics.
Cities with strong sales performance and high listing efficiency
represent potential opportunities for expansion, while areas with lower
efficiency may require improvements in pricing strategies, property
promotion and market positioning.
The statistical insights obtained
from this analysis can support more informed decisions and improve the
effectiveness of real estate management strategies.