Files
MAT12005-Project-in-Data-An…/Task 1-4.r
T
2026-06-24 16:37:45 +02:00

368 lines
16 KiB
R
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Task 1
# a)
library(ggplot2)
pkseutu <- read.csv("pkseutu2015.csv", encoding = "latin1")
ggplot(data = pkseutu) +
geom_point(mapping = aes(x = Prosentti, y = KOK)) +
labs(
title = "Support for KOK versus turnout",
x = "Voter turnout (%)",
y = "KOK support (%)"
)
ggplot(data = pkseutu) +
geom_point(mapping = aes(x = Prosentti, y = SDP)) +
labs(
title = "Support for SDP versus turnout",
x = "Voter turnout (%)",
y = "SDP support (%)"
)
ggplot(data = pkseutu) +
geom_point(mapping = aes(x = Prosentti, y = PS)) +
labs(
title = "Support for PS versus turnout",
x = "Voter turnout (%)",
y = "PS support (%)"
)
ggplot(data = pkseutu) +
geom_point(mapping = aes(x = Prosentti, y = KESK)) +
labs(
title = "Support for KESK versus turnout",
x = "Voter turnout (%)",
y = "KESK support (%)"
)
ggplot(data = pkseutu) +
geom_point(mapping = aes(x = Prosentti, y = VAS)) +
labs(
title = "Support for VAS versus turnout",
x = "Voter turnout (%)",
y = "VAS support (%)"
)
ggplot(data = pkseutu) +
geom_point(mapping = aes(x = Prosentti, y = VIHR)) +
labs(
title = "Support for VIHR versus turnout",
x = "Voter turnout (%)",
y = "VIHR support (%)"
)
ggplot(data = pkseutu) +
geom_point(mapping = aes(x = Prosentti, y = RKP)) +
labs(
title = "Support for RKP versus turnout",
x = "Voter turnout (%)",
y = "RKP support (%)"
)
ggplot(data = pkseutu) +
geom_point(mapping = aes(x = Prosentti, y = KD)) +
labs(
title = "Support for KD versus turnout",
x = "Voter turnout (%)",
y = "KD support (%)"
)
ggplot(data = pkseutu) +
geom_point(mapping = aes(x = Prosentti, y = Muut)) +
labs(
title = "Support for Muut versus turnout",
x = "Voter turnout (%)",
y = "Muut support (%)"
)
# The party whose support seems to be growing as the turnout increases is KOK, as is shown in the first scatterplot.
# Based on the first scattersplot, i) is the only interpreation that can be made. This is because there seems to be a strong positive correlation between voter turnout and support for KOK.
# ii) cannot be made, since correlation does not imply causation. High voter turnout seems to correlate to, but does not guarantee, high support for KOK. "Obviously" is a strong adverb and can only be used if there is a strict proof of causation.
# iii) cannot be made either. Again, correlation does not imply causation. Support for KOK seems to correlate to, but does not guarantee, high voter turnout. The "obviously" adverb cannot be used for the same reason as above.
# b)
# In my opinion, the support for SDP seems to explain well the support for PS.
# We can plot the support for the two parties and add a line of best fit as follows:
ggplot(data = pkseutu, aes(x = SDP, y = PS)) +
geom_point() +
geom_smooth(method = "lm")
# Creating a linear regression model to explain support for PS using support for SDP as predictor
model <- lm(PS ~ SDP, data = pkseutu)
predicted_PS_support <- as.numeric(predict(model, data.frame(SDP = 15.5)))
print(predicted_PS_support) # 14.13686
# => Where the explanatory party's support, i.e. SDP's support, is 15.5%, my model predicts support for PS to be 14.13686%.
# c)
# The exception is Otaniemi, as is demonstrated below:
ggplot(data = pkseutu, aes(x = Muut, y = KOK, label = Alue)) +
geom_text()
filtered_pkseutu <- pkseutu[pkseutu$Alue != "Otaniemi", ]
model_2 <- lm(KOK ~ Muut, data = filtered_pkseutu)
filtered_pkseutu$KOK_predicted <- predict(model_2)
ggplot(data = filtered_pkseutu, aes(x = Muut, y = KOK)) +
geom_point() +
geom_line(aes(y = KOK_predicted), color = "blue")
all_parties <- colnames(filtered_pkseutu)[3:length(colnames(filtered_pkseutu))]
all_parties_except_kok_and_muut <- all_parties[all_parties != "KOK" & all_parties != "Muut" & all_parties != "KOK_predicted"]
max_R_squared <- -Inf
selected_party <- ""
selected_model <- NA
filtered_pkseutu <- pkseutu[pkseutu$Alue != "Otaniemi", ]
for (party in all_parties_except_kok_and_muut) {
formula <- as.formula(paste("KOK ~ Muut +", party))
new_model <- lm(formula, data = filtered_pkseutu)
new_R_squared <- summary(new_model)$r.squared
if (new_R_squared > max_R_squared) {
max_R_squared <- new_R_squared
selected_party <- party
selected_model <- new_model
}
}
print(max_R_squared) # 0.7871733
print(selected_party) # SDP
summary(selected_model)
predicted_KOK_support <- as.numeric(predict(selected_model, data.frame(Muut = 3.5, SDP = 10.2)))
predicted_KOK_support # 29.31328
# => The support for KOK is predicted to be 29.31328% when Muut support is 3.5% and SDP support is 10.2% in my model.
# This model is particularly suitable because the coefficient of determination is relatively, but not overfittingly, high, at 0.787.
# This means that roughly 78.7% of the variability in the support for KOK can be explained by the support for Muut and the support for SDP.
# d)
model_3 <- lm(KOK ~ SDP + PS + KESK + VAS + VIHR + RKP + KD + Muut, data = pkseutu)
ggplot(data = pkseutu, aes(x = SDP + PS + KESK + VAS + VIHR + RKP + KD + Muut, y = KOK)) +
geom_point() +
geom_smooth(method = "lm")
# I notice that there is a very strong negative linear correlation between the support for KOK and the support all other parties, and the model seems to fit perfectly.
# This is entirely understandable, because when we group all other parties together, there is now only two choices: vote for KOK or vote for any other party.
# Any vote for KOK means one less vote for the other parties, and vice versa.
# Thus, the correlation between the support for KOK and the support for the other parties must be perfectly negative.
# Task 2
finland <- read.csv("ek2023.csv", encoding = "latin1")
hki <- read.csv("hki2019.csv", encoding = "latin1")
# a)
largestSupport <- function(df, party) {
percentages <- df[[party]]
if (length(percentages) == 0) {
return("Please check the party name")
}
max_support <- max(percentages, na.rm = TRUE)
max_index <- which.max(percentages)
max_area <- df[max_index, "Alue"]
return(paste(party, "support is highest in the", max_area, "region and its support is", max_support, "percent"))
}
print(largestSupport(hki, "PIR"))
print(largestSupport(finland, "SDP"))
print(largestSupport(hki, "XYZ"))
# b)
largestParty <- function(df, region) {
row <- subset(df, Alue == region)
if (length(rownames(row)) == 0) {
return("The area not found")
}
numeric_row <- row[, sapply(row, is.numeric)]
max_support <- max(numeric_row)
max_party <- names(numeric_row)[which.max(numeric_row)]
return(paste("The largest party in the", region, "region is", max_party, "and its support is", max_support, "percent"))
}
print(largestParty(hki, "Katajanokka"))
print(largestParty(finland, "Närpiö"))
print(largestParty(finland, "Exactum"))
# c)
visualize <- function(df, party_1, party_2) {
ggplot(df) +
geom_point(mapping = aes(x = .data[[party_1]], y = .data[[party_2]], colour = .data[["Suuralue"]])) +
labs(x = paste("Support for", party_1, "(%)"), y = paste("Support for", party_2, "(%)"), colour = "Major Area") +
ggtitle(paste("Support for", party_1, "vs support for", party_2, "in Helsinki's voting areas")) +
theme_minimal()
}
visualize(hki, "VAS", "KOK")
# The picture shows a negative linear correlation between the support for the Left Alliance and the support for the National Coalition Party. Areas with higher support for the Left Alliance tends to have lower support for the National Coalition Party, and vice-versa.
# For instance, areas in the South (Eteläinen) mostly have higher support for the National Coalition Party (20-40%) but lower support for the Left Alliance (5-10%).
# Conversely, areas in the Central (Keskinen) have higher support for the Left Alliance (15-25%) but lower support for the National Coalition Party (5-15%).
# d)
compare <- function(df, region, reference_region, k) {
row <- subset(df, Alue == region)
numeric_row <- as.numeric(row[, sapply(row, is.numeric)])
parties <- colnames(row[, sapply(row, is.numeric)])
sorted_support <- sort(numeric_row, decreasing = TRUE)
selected_parties <- c()
for (support_value in sorted_support[1:k]) {
party_index <- which(numeric_row == support_value)
selected_parties <- c(selected_parties, parties[party_index])
}
shortened_row_1 <- row[, selected_parties, drop = FALSE]
row_2 <- subset(df, Alue == reference_region)
shortened_row_2 <- row_2[, selected_parties, drop = FALSE]
combined_df <- data.frame(
Party = selected_parties,
Region = rep(c(region, reference_region), each = k),
Support = c(as.numeric(shortened_row_1), as.numeric(shortened_row_2))
)
ggplot(combined_df, aes(x = Party, y = Support, fill = Region)) +
geom_bar(stat = "identity", position = "dodge") +
labs(
title = paste("Support for", k, "largest parties in", region, "vs in", reference_region),
x = "Party",
y = "Support (%)"
) +
theme_minimal()
}
compare(finland, "Helsinki", "Oulu", 10)
# Task 3
# a)
density_function_of_prior_distribution <- function(theta) {
if (theta > 0 && theta <= 1/100) {
return(200 * theta)
} else if (theta > 1/100 && theta <= 1) {
return(20000/9900 * (1 - theta))
} else {
return(0)
}
}
theta_values <- seq(0, 1, length.out = 9999)
prior_values <- sapply(theta_values, density_function_of_prior_distribution)
ggplot(data.frame(theta = theta_values, density = prior_values), aes(x = theta, y = density)) +
geom_line(color = "blue") +
labs(title = "Prior Distribution of θ", x = "θ", y = "Density") +
theme_minimal()
# The prior distribution tells us the following about Mr. Fishers prior expectations about θ:
# - θ, that is, the probability of the die landing on 1000, is probably very small, since the density function peaks at a value very close to 0.
# - Although Mr. Fisher suspects that the die is weighted and more likely to land on 1000 than on any other number, he only suspects the die to be slightly weighted and not heavily biased, since after the peak, the density function declines linearly without fail as θ gets larger.
# - If the die were unbiased, θ would be exactly 1/1000. Since the density function peaks when θ is at 1/100, it means Mr. Fisher suspects that θ, i.e., the probability of the die landing on 1000, is most likely to be 10 times more than the probability of the die landing on any other number.
# b)
k <- 1234
n <- 234321
log_of_posterior_values <- sapply(theta_values, function(theta) {
log_prior <- log(density_function_of_prior_distribution(theta))
if (log_prior == -Inf) return(-Inf)
log_likelihood <- k * log(theta) + (n - k) * log(1 - theta)
return(log_prior + log_likelihood)
})
map_estimate <- theta_values[which.max(log_of_posterior_values)]
print(map_estimate) # 0.00530106
plot(
theta_values,
log_of_posterior_values,
type = "l",
col = "blue",
lwd = 4,
main = "Logarithm of the Posterior Distribution of θ",
xlab = "θ",
ylab = "Logarithm of Density"
)
abline(v = map_estimate, col = "red", lty = 2)
legend("topright", legend = paste("MAP Estimate: θ ≈", map_estimate), col = "red", lty = 2)
# Since the maximum a posteriori probability estimate is calculated to be 0.0053,
# that is also what Mr. Fisher thinks is the best estimate for θ.
# If the prior distribution had been a uniform distribution, the estimate would have most likely been different, specifically, higher.
# This is because in our non-uniform example, Mr. Fisher believes that the die is only slightly weighted, i.e., he believes
# that θ most likely has a small value, as we already saw in part a) that the density function peaked at a small value near 0.
# This, in turn, causes the MAP estimate to also be a small value near 0. If the prior distribution had been uniform,
# the density function in part a) would have likely peaked at a larger value, and so the MAP estimate would have also been larger.
# The posterior distribution should be logarithmised because:
# 1. The values in the posterior distribution might be very small, causing underflow errors when performing calculations. Logarithmising these values stabilises them in a range that is generally higher, helping us avoid calculation errors.
# 2. Doing so makes calculating the MAP estimate easier. In order to calculate the MAP, we have to find the value of theta that would
# maximise the value of the density function of the posterior distribution. Doing this often requires differentiating the density function.
# Since the density function might be complex in the posterior distribution, differentiating it is difficult, and so is calculating the MAP estimate.
# Logarithmising the posterior distribution, however, gives us a "smooth" curve, which is much easier to differentiate.
# Task 4
# a)
set.seed(1)
options(scipen = 999)
lotterymachine <- function(n) {
p = 1/18643
return(rbinom(1, n, p))
}
number_of_rounds <- 10000
tickets_per_round <- 50
total_tickets <- number_of_rounds * tickets_per_round
results <- replicate(number_of_rounds, lotterymachine(tickets_per_round))
# ML estimate for a one-ticket winning probability
ML_estimate = sum(results)/total_tickets
print(ML_estimate) # 0.000032
ste <- sqrt(ML_estimate * (1 - ML_estimate) / total_tickets)
z <- 1.96
moe <- z * ste
lower <- ML_estimate - moe
upper <- ML_estimate + moe
lower # 0.00001632025
upper # 0.00004767975
print(paste0("Our 95% Confidence Interval is: (", lower,", ", upper, ")"))
# Our 95% Confidence Interval is: (0.0000163202508820071, 0.0000476797491179929)
real_probability <- 1/18643
print(real_probability) # 0.00005363944
print(lower <= real_probability && real_probability <= upper) # FALSE
# => Our 95% Confidence Interval does not include the probability of winning.
# If we increase the number of lottery rounds, our confidence interval should become narrower and thus more accurate.
# This is because adding more lottery rounds makes the number of total tickets, and therefore the standard error, i.e. sqrt(ML_estimate * (1 - ML_estimate) / total_tickets), smaller,
# which in turn makes the margin of error smaller.
# If we increase the number of tickets played in each round, the total number of tickets would increase, making the standard error smaller,
# making the margin of error smaller, and thus also making the confidence interval narrower.
# b)
number_of_simulations <- 100
ML_estimates <- numeric(number_of_simulations)
for (i in 1:number_of_simulations) {
simulation_results <- replicate(number_of_rounds, lotterymachine(tickets_per_round))
this_round_ML_estimate = sum(simulation_results)/total_tickets
ML_estimates[i] <- this_round_ML_estimate
}
sorted_ML_estimates = sort(ML_estimates)
lower_2 <- sorted_ML_estimates[round(0.025 * number_of_simulations)]
upper_2 <- sorted_ML_estimates[round(0.975 * number_of_simulations)]
lower_2 # 0.000032
upper_2 # 0.00007
print(paste0("The smallest interval containing 95% of the ML estimates is: (", lower_2, ", ", upper_2, ")"))
# The smallest interval containing 95% of the ML estimates is: (0.000032, 0.00007)
# How do you interpret the above described matter?
# We can interpret our result to mean that if we continue to perform the simulations in part a) an infinite number of times
# and calculate the ML estimate for each simulation, approximately 95% of those estimates will fall into the range (0.000032, 0.00007)
# What happens if you increase the number of simulations?
# If we increase the number of simulations, our interval gets narrower and more accurate as the margin of error becomes smaller.
# As the number of simulations approaches infinity, the percentage of the estimates falling into the interval will approach 95%.
# This is due to the law of large numbers.
# What if you increase the number of tickets played each round?
# If we increase the number of tickets played each round, our interval also gets narrower and more accurate.
# This is also due to the law of large numbers.