This single R Markdown file contains Task 1, Task 2, and Task 3. Knit to HTML.

Task 1

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

Descriptive statistics (Amazon dataset)

## 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'

Task 2

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

## 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

Explanation:

The estimated regression function is:

Price = 2502.47 − 8.67 × Age − 24.06 × Distance

  • The coefficient for Age is −8.67, which means that for each additional year of apartment age, the average price per m² decreases by approximately 8.67 EUR, assuming distance stays constant.
  • The coefficient for Distance is −24.06, indicating that for every additional kilometer away from the city center, the average price per m² decreases by 24.06 EUR, assuming age remains constant.
  • The Multiple R-squared is 0.536, which means that 53.6% of the variation in price is explained by apartment age and distance together.
# 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

Explanation:

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.

  • H₀ (null hypothesis): Model 1 (fit2) is better – fewer variables
  • H₁ (alternative hypothesis): Model 2 (fit3) is better – more variables

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

Explanation of categorical coefficients & F-statistics:

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.

F-statistic hypothesis:

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.

Explanation

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.