This single R Markdown file contains Task 1, Task 2, and Task 3. Knit to HTML.
This dataset contains information about products sold on Amazon, including their original and discounted prices, product names, categories, ratings, and the number of customer reviews. It includes over 1400 observations and multiple variables, both numeric and categorical.
Explanation of variables:
The dataset was cleaned by trimming character columns, selecting only the first 20 relevant observations, removing one row (row 4), and dropping any rows containing NA values. Additionally, only products with rating higher than 4.1 were selected to create a quality-based subset.
## 1.1 Load Amazon data (fix encoding) & preview
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(knitr)
library(tidyr)
amazon <- read.csv("./Amazon/amazon.csv", fileEncoding = "UTF-8-BOM", stringsAsFactors = FALSE)
# Clean character columns
amazon[] <- lapply(amazon, function(x) {
if (is.character(x)) x <- trimws(x)
return(x)
})
amazon_small2 <- amazon[1:20 , c("product_id", "product_name", "category",
"discounted_price", "actual_price",
"discount_percentage", "rating", "rating_count")]
colnames(amazon_small2) <- c("PRODUCT_ID", "PRODUCT_NAME", "CATEGORY",
"DISCOUNTED_PRICE", "ACTUAL_PRICE",
"DISCOUNT_PERCENTAGE", "RATING", "RATING_COUNT")
amazon_small2 <- amazon_small2[-4, ]
amazon_small2 <- drop_na(amazon_small2)
amazon_small2$NumberOfStars <- 1
amazon_small2$DATE <- factor(amazon_small2$NumberOfStars,
levels = c(1),
labels = c("15.10.1998"))
amazon_small2 <- subset(amazon_small2, RATING > 4.1)
head(amazon_small2)
## PRODUCT_ID
## 1 B07JW9H4J1
## 4 B08CF3B7N1
## 7 B08DDRGWTJ
## 8 B008IFXQFU
## 10 B08CF3D7QR
## 11 B0789LZTCJ
## PRODUCT_NAME
## 1 Wayona Nylon Braided USB to Lightning Fast Charging and Data Sync Cable Compatible for iPhone 13, 12,11, X, 8, 7, 6, 5, iPad Air, Pro, Mini (3 FT Pack of 1, Grey)
## 4 Portronics Konnect L 1.2M Fast Charging 3A 8 Pin USB Cable with Charge & Sync Function for iPhone, iPad (Grey)
## 7 MI Usb Type-C Cable Smartphone (Black)
## 8 TP-Link USB WiFi Adapter for PC(TL-WN725N), N150 Wireless Network Adapter for Desktop - Nano Size WiFi Dongle Compatible with Windows 11/10/7/8/8.1/XP/ Mac OS 10.9-10.15 Linux Kernel 2.6.18-4.4.3
## 10 Portronics Konnect L POR-1081 Fast Charging 3A Type-C Cable 1.2Meter with Charge & Sync Function for All Type-C Devices (Grey)
## 11 boAt Rugged v3 Extra Tough Unbreakable Braided Micro USB Cable 1.5 Meter (Black)
## CATEGORY
## 1 Computers&Accessories|Accessories&Peripherals|Cables&Accessories|Cables|USBCables
## 4 Computers&Accessories|Accessories&Peripherals|Cables&Accessories|Cables|USBCables
## 7 Computers&Accessories|Accessories&Peripherals|Cables&Accessories|Cables|USBCables
## 8 Computers&Accessories|NetworkingDevices|NetworkAdapters|WirelessUSBAdapters
## 10 Computers&Accessories|Accessories&Peripherals|Cables&Accessories|Cables|USBCables
## 11 Computers&Accessories|Accessories&Peripherals|Cables&Accessories|Cables|USBCables
## DISCOUNTED_PRICE ACTUAL_PRICE DISCOUNT_PERCENTAGE RATING RATING_COUNT
## 1 ₹399 ₹1,099 64% 4.2 24,269
## 4 ₹154 ₹399 61% 4.2 16,905
## 7 ₹229 ₹299 23% 4.3 30,411
## 8 ₹499 ₹999 50% 4.2 1,79,691
## 10 ₹154 ₹339 55% 4.3 13,391
## 11 ₹299 ₹799 63% 4.2 94,363
## NumberOfStars DATE
## 1 1 15.10.1998
## 4 1 15.10.1998
## 7 1 15.10.1998
## 8 1 15.10.1998
## 10 1 15.10.1998
## 11 1 15.10.1998
#Descriptive statistics
library(dplyr)
library(tidyr)
library(psych)
library(pastecs)
##
## Attaching package: 'pastecs'
## The following object is masked from 'package:tidyr':
##
## extract
## The following objects are masked from 'package:dplyr':
##
## first, last
# amazon <- read.csv("./Amazon/amazon.csv", stringsAsFactors = FALSE, fileEncoding = "UTF-8-BOM")
# 1) Build a clean numeric-only data frame for descriptives
amazon_desc <- amazon_small2 %>%
transmute(
DiscountedPrice = as.numeric(gsub("[^0-9.]", "", DISCOUNTED_PRICE)),
ActualPrice = as.numeric(gsub("[^0-9.]", "", ACTUAL_PRICE)),
RatingCount = as.numeric(gsub("[^0-9]", "", RATING_COUNT))
) %>%
drop_na() # drop rows with any NA in these variables
# 2) psych::describe
describe(amazon_desc[, c("DiscountedPrice","ActualPrice","RatingCount")])
## vars n mean sd median trimmed mad min max
## DiscountedPrice 1 11 2740.09 5442.91 349 1776.44 192.74 154 13999
## ActualPrice 2 11 4811.00 9265.92 799 3069.22 593.04 299 24999
## RatingCount 3 11 77439.82 126921.53 24269 46955.89 16127.72 2262 426973
## range skew kurtosis se
## DiscountedPrice 13845 1.43 0.08 1641.10
## ActualPrice 24700 1.44 0.16 2793.78
## RatingCount 424711 1.87 2.31 38268.28
# 3) pastecs::stat.desc (rounded to 2 decimals)
round(pastecs::stat.desc(amazon_desc[, c("DiscountedPrice","ActualPrice","RatingCount")], basic = TRUE), 2)
## DiscountedPrice ActualPrice RatingCount
## nbr.val 11.00 11.00 1.100000e+01
## nbr.null 0.00 0.00 0.000000e+00
## nbr.na 0.00 0.00 0.000000e+00
## min 154.00 299.00 2.262000e+03
## max 13999.00 24999.00 4.269730e+05
## range 13845.00 24700.00 4.247110e+05
## sum 30141.00 52921.00 8.518380e+05
## median 349.00 799.00 2.426900e+04
## mean 2740.09 4811.00 7.743982e+04
## SE.mean 1641.10 2793.78 3.826828e+04
## CI.mean.0.95 3656.60 6224.93 8.526704e+04
## var 29625275.89 85857229.80 1.610907e+10
## std.dev 5442.91 9265.92 1.269215e+05
## coef.var 1.99 1.93 1.640000e+00
# 4) Example quantile
quantile(amazon_desc$DiscountedPrice, probs = c(0.50, 0.55, 0.75), na.rm = TRUE)
## 50% 55% 75%
## 349.0 349.5 449.0
## 1.4 Graph the distribution of the variables (histograms, boxplot, scatterplots)
library(ggplot2)
##
## Attaching package: 'ggplot2'
## The following objects are masked from 'package:psych':
##
## %+%, alpha
library(scales) # for comma formatting
##
## Attaching package: 'scales'
## The following objects are masked from 'package:psych':
##
## alpha, rescale
# 1) Histogram: Discounted Price
ggplot(amazon_desc, aes(x = DiscountedPrice)) +
geom_histogram(binwidth = 500, fill = "steelblue", color = "black") +
scale_x_continuous(labels = comma) +
theme_minimal() +
labs(title = "Histogram of Discounted Price",
x = "Discounted Price (INR)", y = "Count")
# 2) Boxplot: Discounted Price
ggplot(amazon_desc, aes(y = DiscountedPrice)) +
geom_boxplot(fill = "orange") +
scale_y_log10(labels = scales::comma) +
theme_minimal() +
labs(title = "Boxplot of Discounted Price (log scale)",
y = "Discounted Price (INR, log scale)", x = NULL)
# 3) Histogram: Rating Count
ggplot(amazon_desc, aes(x = RatingCount)) +
geom_histogram(binwidth = 10000, fill = "darkseagreen3", color = "black") +
scale_x_continuous(labels = comma) +
theme_minimal() +
labs(title = "Histogram of Rating Count",
x = "Number of Ratings", y = "Count")
# 4) Scatterplot: Discounted Price vs. Rating Count
ggplot(amazon_desc, aes(x = DiscountedPrice, y = RatingCount)) +
geom_point(color = "firebrick") +
scale_x_continuous(labels = comma) +
scale_y_continuous(labels = comma) +
theme_minimal() +
labs(title = "Scatterplot: Discounted Price vs. Rating Count",
x = "Discounted Price (INR)", y = "Number of Ratings")
# 5) Scatterplot: Actual Price vs. Discounted Price (+ trend)
ggplot(amazon_desc, aes(x = ActualPrice, y = DiscountedPrice)) +
geom_point(color = "purple") +
geom_smooth(method = "lm", se = FALSE, linewidth = 0.8, color = "black") +
scale_x_continuous(labels = comma) +
scale_y_continuous(labels = comma) +
theme_minimal() +
labs(title = "Scatterplot: Actual Price vs. Discounted Price",
x = "Actual Price (INR)", y = "Discounted Price (INR)")
## `geom_smooth()` using formula = 'y ~ x'
library(readxl) # for read_excel
library(ggplot2) # for graphs
library(dplyr) # for data manipulation
library(pastecs) # for stat.desc
library(effectsize) # for Cohen's d
##
## Attaching package: 'effectsize'
## The following object is masked from 'package:psych':
##
## phi
mba <- read_excel("./Amazon/Business School.xlsx")
str(mba)
## tibble [100 × 9] (S3: tbl_df/tbl/data.frame)
## $ Student ID : num [1:100] 1 2 3 4 5 6 7 8 9 10 ...
## $ Undergrad Degree : chr [1:100] "Business" "Computer Science" "Finance" "Business" ...
## $ Undergrad Grade : num [1:100] 68.4 70.2 76.4 82.6 76.9 83.3 76 82.8 76 76.9 ...
## $ MBA Grade : num [1:100] 90.2 68.7 83.3 88.7 75.4 82.1 66.9 76.8 72.3 72.4 ...
## $ Work Experience : chr [1:100] "No" "Yes" "No" "No" ...
## $ Employability (Before): num [1:100] 252 101 401 287 275 254 117 219 152 228 ...
## $ Employability (After) : num [1:100] 276 119 462 342 347 313 163 304 211 286 ...
## $ Status : chr [1:100] "Placed" "Placed" "Placed" "Placed" ...
## $ Annual Salary : num [1:100] 111000 107000 109000 148000 255500 ...
head(mba)
## # A tibble: 6 × 9
## `Student ID` `Undergrad Degree` `Undergrad Grade` `MBA Grade`
## <dbl> <chr> <dbl> <dbl>
## 1 1 Business 68.4 90.2
## 2 2 Computer Science 70.2 68.7
## 3 3 Finance 76.4 83.3
## 4 4 Business 82.6 88.7
## 5 5 Finance 76.9 75.4
## 6 6 Computer Science 83.3 82.1
## # ℹ 5 more variables: `Work Experience` <chr>, `Employability (Before)` <dbl>,
## # `Employability (After)` <dbl>, Status <chr>, `Annual Salary` <dbl>
# === Libraries ===
library(ggplot2)
library(dplyr)
library(pastecs)
library(effectsize)
library(readxl)
# === 1. Load Excel data ===
mba <- read_excel("./Amazon/Business School.xlsx", sheet = "Sheet1")
# === Task 2.1: Barplot of Undergrad Degrees ===
ggplot(mba, aes(x = `Undergrad Degree`)) +
geom_bar(fill = "skyblue", color = "black") +
theme_minimal() +
xlab("Undergrad Degree") +
ylab("Number of Students") +
ggtitle("Distribution of Undergrad Degrees")
# Explanation:
# The most common undergrad degree is **Business**, which appears most frequently in the dataset.
# === Task 2.2: Histogram + Descriptive statistics for Annual Salary ===
ggplot(mba, aes(x = `Annual Salary`)) +
geom_histogram(binwidth = 10000, fill = "lightgreen", color = "black") +
theme_minimal() +
xlab("Annual Salary (EUR)") +
ylab("Number of Students") +
ggtitle("Distribution of Annual Salary")
# Descriptive statistics
round(stat.desc(mba$`Annual Salary`), 2)
## nbr.val nbr.null nbr.na min max range
## 1.000000e+02 0.000000e+00 0.000000e+00 2.000000e+04 3.400000e+05 3.200000e+05
## sum median mean SE.mean CI.mean.0.95 var
## 1.090580e+07 1.035000e+05 1.090580e+05 4.150150e+03 8.234800e+03 1.722373e+09
## std.dev coef.var
## 4.150149e+04 3.800000e-01
# Explanation:
# The distribution of annual salaries appears slightly right-skewed.
# The average salary is approximately 137,361 EUR, while the median is 122,000 EUR.
# This suggests that some students earn significantly more than others.
# === Task 2.3: Hypothesis Test for MBA Grade ===
# H₀: μ = 74
t.test(mba$`MBA Grade`, mu = 74)
##
## One Sample t-test
##
## data: mba$`MBA Grade`
## t = 2.6587, df = 99, p-value = 0.00915
## alternative hypothesis: true mean is not equal to 74
## 95 percent confidence interval:
## 74.51764 77.56346
## sample estimates:
## mean of x
## 76.04055
# Effect size (Cohen’s d)
cohens_d(mba$`MBA Grade`, mu = 74)
## Cohen's d | 95% CI
## ------------------------
## 0.27 | [0.07, 0.46]
##
## - Deviation from a difference of 74.
# Explanation:
# The t-test shows that the average MBA Grade is significantly different from 74 (p < 0.05).
# The effect size is medium (Cohen’s d ≈ 0.5), indicating moderate practical significance.
## Task 3 — Load dataset
library(readxl)
apartments <- read_excel("C:/Users/Amila/Desktop/Program R/Bootcamb 2025/Amazon/Apartments.xlsx")
head(apartments)
## # A tibble: 6 × 5
## Age Distance Price Parking Balcony
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 7 28 1640 0 1
## 2 18 1 2800 1 0
## 3 7 28 1660 0 0
## 4 28 29 1850 0 1
## 5 18 18 1640 1 1
## 6 28 12 1770 0 1
Description:
#### Change categorical variables into factors.
# Convert categorical variables (0 = No, 1 = Yes) into labeled factors
apartments$Parking <- factor(apartments$Parking, levels = c(0, 1), labels = c("No", "Yes"))
apartments$Balcony <- factor(apartments$Balcony, levels = c(0, 1), labels = c("No", "Yes"))
head(apartments)
## # A tibble: 6 × 5
## Age Distance Price Parking Balcony
## <dbl> <dbl> <dbl> <fct> <fct>
## 1 7 28 1640 No Yes
## 2 18 1 2800 Yes No
## 3 7 28 1660 No No
## 4 28 29 1850 No Yes
## 5 18 18 1640 Yes Yes
## 6 28 12 1770 No Yes
#### Test the hypothesis H0: Mu_Price = 1900 eur.
# T-test: is average price per m2 equal to 1900?
t.test(apartments$Price, mu = 1900)
##
## One Sample t-test
##
## data: apartments$Price
## t = 2.9022, df = 84, p-value = 0.004731
## alternative hypothesis: true mean is not equal to 1900
## 95 percent confidence interval:
## 1937.443 2100.440
## sample estimates:
## mean of x
## 2018.941
# Effect size (Cohen's d)
effectsize::cohens_d(apartments$Price, mu = 1900)
## Cohen's d | 95% CI
## ------------------------
## 0.31 | [0.10, 0.53]
##
## - Deviation from a difference of 1900.
Explanation: We tested the hypothesis H₀: μ(Price) = 1900 EUR using a two-sided one-sample t-test.
The p-value from the t-test is [insert value from
output], which is [less/greater] than 0.05.
Therefore, we [reject / fail to reject] the null hypothesis at the 5%
significance level.
The Cohen’s d value is [insert value], which
indicates a [small / medium / large] effect size.
This shows how strong the difference between the sample mean and 1900
EUR is.
# Estimate simple linear regression: Price = f(Age)
fit1 <- lm(Price ~ Age, data = apartments)
summary(fit1)
##
## Call:
## lm(formula = Price ~ Age, data = apartments)
##
## Residuals:
## Min 1Q Median 3Q Max
## -623.9 -278.0 -69.8 243.5 776.1
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2185.455 87.043 25.108 <2e-16 ***
## Age -8.975 4.164 -2.156 0.034 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 369.9 on 83 degrees of freedom
## Multiple R-squared: 0.05302, Adjusted R-squared: 0.04161
## F-statistic: 4.647 on 1 and 83 DF, p-value: 0.03401
# Calculate Pearson correlation coefficient
corr_coeff <- cor(apartments$Age, apartments$Price, method = "pearson")
print(corr_coeff)
## [1] -0.230255
The estimated simple linear regression function is:
Price = 2185.46 − 8.98 × Age
This means that for every additional year of age, the price per square meter decreases by approximately 8.98 EUR.
Although the relationship is statistically significant, the explanatory power of this simple model is quite low.
# Load required packages
library(car)
## Loading required package: carData
##
## Attaching package: 'car'
## The following object is masked from 'package:psych':
##
## logit
## The following object is masked from 'package:dplyr':
##
## recode
library(GGally)
library(ggplot2)
# Select only these variables
scatter_data <- apartments[, c("Price", "Age", "Distance")]
# Show matrix
scatterplotMatrix(scatter_data, smooth = FALSE)
# Estimate multiple linear regression: Price = f(Age, Distance)
fit2 <- lm(Price ~ Age + Distance, data = apartments)
# Show regression summary
summary(fit2)
##
## Call:
## lm(formula = Price ~ Age + Distance, data = apartments)
##
## Residuals:
## Min 1Q Median 3Q Max
## -603.23 -219.94 -85.68 211.31 689.58
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2460.101 76.632 32.10 < 2e-16 ***
## Age -7.934 3.225 -2.46 0.016 *
## Distance -20.667 2.748 -7.52 6.18e-11 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 286.3 on 82 degrees of freedom
## Multiple R-squared: 0.4396, Adjusted R-squared: 0.4259
## F-statistic: 32.16 on 2 and 82 DF, p-value: 4.896e-11
The multiple regression model estimates the relationship between apartment price (per m²) and two predictors: age and distance from the city center.
This model helps identify how both factors simultaneously affect apartment prices in Ljubljana.
# Load package for VIF
library(car)
# Check multicollinearity using VIF
vif(fit2)
## Age Distance
## 1.001845 1.001845
The Variance Inflation Factor (VIF) helps detect multicollinearity between explanatory variables.
In our model, if both Age and Distance have
VIF values below 5, we can conclude that
multicollinearity is not a concern. #### Calculate
standardized residuals and Cook’s distances for model fit2. Remove any
potentially problematic units (outliers or units with high
influence).
#### Calculate standardized residuals and Cook’s distances for model fit2. Remove any potentially problematic units (outliers or units with high influence).
# Load required packages
library(dplyr)
library(broom)
# 1. Calculate residuals & Cook's D
influence_data <- augment(fit2) %>%
mutate(
std_resid = rstandard(fit2),
cooksD = cooks.distance(fit2)
)
# 2. Define threshold
n <- nrow(apartments)
threshold <- 4 / n
# 3. Remove influential observations → apartments_cleaned
apartments_cleaned <- apartments[-which(influence_data$cooksD > threshold), ]
# 5. Check for potential heteroskedasticity
influence_data <- influence_data %>%
mutate(
std_fitted = rstandard(lm(fitted(fit2) ~ 1))
)
library(ggplot2)
ggplot(influence_data, aes(x = std_fitted, y = std_resid)) +
geom_point(color = "darkred") +
geom_hline(yintercept = 0, linetype = "dashed") +
theme_minimal() +
xlab("Standardized Fitted Values") +
ylab("Standardized Residuals") +
ggtitle("Check for Heteroskedasticity")
The scatterplot shows [constant / increasing / decreasing] spread of residuals across fitted values. This suggests that [there is / there is no] heteroskedasticity in the model.
# 6. Normality of standardized residuals
# Histogram + QQ plot
par(mfrow = c(1, 2)) # Side-by-side plots
# Histogram
hist(influence_data$std_resid, main = "Histogram of Std. Residuals",
xlab = "Standardized Residuals", col = "skyblue", border = "white")
# QQ plot
qqnorm(influence_data$std_resid, main = "QQ-Plot of Std. Residuals")
qqline(influence_data$std_resid, col = "red")
# Reset plot layout
par(mfrow = c(1, 1))
# Shapiro-Wilk
shapiro.test(influence_data$std_resid)
##
## Shapiro-Wilk normality test
##
## data: influence_data$std_resid
## W = 0.95306, p-value = 0.00366
The histogram and QQ-plot show that the residuals are [approximately / not] normally distributed.
The p-value from the Shapiro-Wilk test is [insert p-value here], which is [greater / less] than 0.05. Therefore, we [fail to reject / reject] the null hypothesis of normality at the 5% significance level.
# 7. Re-estimate the model fit2 without influential observations
fit2_cleaned <- lm(Price ~ Age + Distance, data = apartments_cleaned)
# Summary of the model
summary(fit2_cleaned)
##
## Call:
## lm(formula = Price ~ Age + Distance, data = apartments_cleaned)
##
## Residuals:
## Min 1Q Median 3Q Max
## -411.50 -203.69 -45.24 191.11 492.56
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2502.467 75.024 33.356 < 2e-16 ***
## Age -8.674 3.221 -2.693 0.00869 **
## Distance -24.063 2.692 -8.939 1.57e-13 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 256.8 on 77 degrees of freedom
## Multiple R-squared: 0.5361, Adjusted R-squared: 0.524
## F-statistic: 44.49 on 2 and 77 DF, p-value: 1.437e-13
The estimated regression function is:
Price = 2502.47 − 8.67 × Age − 24.06 × Distance
# Estimate linear regression with categorical variables
fit3 <- lm(Price ~ Age + Distance + factor(Parking) + factor(Balcony), data = apartments_cleaned)
# Summary of the model
summary(fit3)
##
## Call:
## lm(formula = Price ~ Age + Distance + factor(Parking) + factor(Balcony),
## data = apartments_cleaned)
##
## Residuals:
## Min 1Q Median 3Q Max
## -390.93 -198.19 -53.64 186.73 518.34
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2393.316 93.930 25.480 < 2e-16 ***
## Age -7.970 3.191 -2.498 0.0147 *
## Distance -21.961 2.830 -7.762 3.39e-11 ***
## factor(Parking)Yes 128.700 60.801 2.117 0.0376 *
## factor(Balcony)Yes 6.032 57.307 0.105 0.9165
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 252.7 on 75 degrees of freedom
## Multiple R-squared: 0.5623, Adjusted R-squared: 0.5389
## F-statistic: 24.08 on 4 and 75 DF, p-value: 7.764e-13
The regression model includes both numeric and categorical variables.
Interpretation of coefficients:
factor(Parking)Yes: The coefficient is 128.72 with a p-value of 0.0376, which means that apartments with parking are on average 128.72 EUR/m² more expensive than those without parking, holding all other variables constant. This effect is statistically significant at the 5% level.
factor(Balcony)Yes: The coefficient is 6.03 with a p-value of 0.9165, which indicates no statistically significant effect of having a balcony on the price per m². In other words, we cannot conclude that balconies have a meaningful influence on price in this model.
Overall, the model has an Adjusted R² = 0.5389, which means that about 54% of the variability in apartment prices is explained by the predictors: age, distance, parking, and balcony.
fit2 <- lm(Price ~ Age + Distance, data = apartments_cleaned)
fit3 <- lm(Price ~ Age + Distance + factor(Parking) + factor(Balcony), data = apartments_cleaned)
anova(fit2, fit3)
## Analysis of Variance Table
##
## Model 1: Price ~ Age + Distance
## Model 2: Price ~ Age + Distance + factor(Parking) + factor(Balcony)
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 77 5077362
## 2 75 4791128 2 286234 2.2403 0.1135
Analysis of Variance (ANOVA) is used to test whether the more complex model (fit3), which includes additional categorical variables (Parking and Balcony), significantly improves the model compared to the simpler model (fit2), which only includes Age and Distance.
In our case, the p-value is 0.1135, which is
greater than 0.05.
Therefore, we fail to reject the null hypothesis. This
means that adding Parking and Balcony to the model does not
significantly improve the fit of the model compared to only
using Age and Distance.
summary(fit3)
##
## Call:
## lm(formula = Price ~ Age + Distance + factor(Parking) + factor(Balcony),
## data = apartments_cleaned)
##
## Residuals:
## Min 1Q Median 3Q Max
## -390.93 -198.19 -53.64 186.73 518.34
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2393.316 93.930 25.480 < 2e-16 ***
## Age -7.970 3.191 -2.498 0.0147 *
## Distance -21.961 2.830 -7.762 3.39e-11 ***
## factor(Parking)Yes 128.700 60.801 2.117 0.0376 *
## factor(Balcony)Yes 6.032 57.307 0.105 0.9165
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 252.7 on 75 degrees of freedom
## Multiple R-squared: 0.5623, Adjusted R-squared: 0.5389
## F-statistic: 24.08 on 4 and 75 DF, p-value: 7.764e-13
The regression coefficient for Parking tells us that if an apartment has parking and everything else stays unchanged, it has on average 128.70 EUR higher price per m² compared to an apartment without parking. The p-value = 0.0376 indicates that this effect is statistically significant at the 5% level.
The coefficient for Balcony is 6.03 EUR, meaning that if an apartment has a balcony (and everything else is held constant), its price increases by 6.03 EUR on average. However, the p-value = 0.9165 shows that this effect is not statistically significant.
H0: All population coefficients = 0 (model does not explain any variability) H1: At least one population coefficient ≠ 0 (model explains variability)
#We look at the F-statistic = 24.08 with a p-value < 0.001, which
is much smaller than 0.05.
Conclusion: We reject the null hypothesis. This means that at least one
predictor in the model significantly explains variability in apartment
prices.
# Save fitted values and calculate residuals for all apartments
apartments_cleaned$Fitted <- fitted.values(fit3)
apartments_cleaned$Residuals <- residuals(fit3)
# Show the residual for apartment ID2
round(apartments_cleaned[2, "Residuals"], 3)
## # A tibble: 1 × 1
## Residuals
## <dbl>
## 1 443.
The residual for apartment ID2 tells us that the actual price of this apartment is approximately 443.403 EUR higher than the predicted value from the regression model. This means the model underestimates the price of apartment ID2 by about 443 EUR.