Για την ανάλυση επιλέχθηκε το dataset Microsoft Stock Data το οποίο περιλαμβάνει ιστορικά δεδομένα της μετοχής της Microsoft. Περιέχει πληροφορίες για την καθημερινή τιμή της μετοχής στο χρηματιστήριο.
Date: η ημερομηνία της συναλλαγής
Open: η τιμή της μετοχής όταν άνοιξε το χρηματιστήριο την συγκεκριμένη ημέρα
High: η υψηλότερη τιμή που έφτασε η μετοχή μέσα στη μέρα
Low: η χαμηλότερη τιμή της μετοχής μέσα στη μέρα
Close: η τιμή της μετοχής όταν έκλεισε το χρηματιστήριο
Adj Close: τιμή κλεισίματος προσαρμοσμένη ώστε να αντικατοπτρίζει την αξία μετά τον υπολογισμό τυχόν εταιρικών ενεργειών
Volume: ο αριθμός των συναλλαγών που πραγματοποιήθηκαν εκείνη την ημέρα
# Εισαγωγή του dataset
MSFT <- read.csv("MSFT.csv")
# Μετατροπή της στήλης Date σε ημερομηνία
MSFT$Date <- as.Date(MSFT$Date)Δημιουργώ μία επιπλέον μεταβλητή:
dailyRange: η διαφορά High - Low, που εκφράζει πόσο άλλαξε η μετοχή μέσα στην ημέρα
(Μέτρο Ενδοημερήσιας Μεταβλητότητας)
# εμφάνιση των δεδομένων σε μορφή πίνακα, ώστε να είναι εύκολη η οπτική εξέτασή τους
View(MSFT)
# παρουσίαση των βασικών στατιστικών στοιχείων για κάθε μεταβλητή
summary(MSFT)## Date Open High
## Min. :1986-03-13 Min. : 0.08854 Min. : 0.09201
## 1st Qu.:1995-03-06 1st Qu.: 4.05078 1st Qu.: 4.10205
## Median :2004-03-11 Median : 26.82000 Median : 27.10000
## Mean :2004-03-12 Mean : 41.32494 Mean : 41.76089
## 3rd Qu.:2013-03-19 3rd Qu.: 40.03500 3rd Qu.: 40.44375
## Max. :2022-03-24 Max. :344.62000 Max. :349.67001
## Low Close Adj.Close
## Min. : 0.08854 Min. : 0.09028 Min. : 0.05705
## 1st Qu.: 4.02734 1st Qu.: 4.07520 1st Qu.: 2.57509
## Median : 26.52000 Median : 26.84000 Median : 18.94853
## Mean : 40.87849 Mean : 41.33563 Mean : 36.25612
## 3rd Qu.: 39.50000 3rd Qu.: 39.93750 3rd Qu.: 29.24481
## Max. :342.20001 Max. :343.10998 Max. :342.40201
## Volume Daily_Range
## Min. :2.304e+06 Min. : 0.000868
## 1st Qu.:3.461e+07 1st Qu.: 0.109375
## Median :5.203e+07 Median : 0.470000
## Mean :5.875e+07 Mean : 0.882398
## 3rd Qu.:7.265e+07 3rd Qu.: 0.940003
## Max. :1.032e+09 Max. :23.640015
## 'data.frame': 9083 obs. of 8 variables:
## $ Date : Date, format: "1986-03-13" "1986-03-14" ...
## $ Open : num 0.0885 0.0972 0.1007 0.1024 0.0998 ...
## $ High : num 0.102 0.102 0.103 0.103 0.101 ...
## $ Low : num 0.0885 0.0972 0.1007 0.099 0.0972 ...
## $ Close : num 0.0972 0.1007 0.1024 0.0998 0.0981 ...
## $ Adj.Close : num 0.0614 0.0636 0.0647 0.0631 0.062 ...
## $ Volume : int 1031788800 308160000 133171200 67766400 47894400 58435200 59990400 65289600 32083200 22752000 ...
## $ Daily_Range: num 0.01302 0.00521 0.0026 0.00434 0.00347 ...
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.0 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.2 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
ggplot(data = MSFT, aes(x = Open, y = Close)) +
geom_point(size = 3, alpha = 0.7, color = "blue") +
labs(
title = "Τιμή Ανοίγματος vs Τιμή Κλεισίματος Μετοχής Microsoft",
x = "Τιμή Ανοίγματος",
y = "Τιμή Κλεισίματος"
) +
theme_classic()Σχολιασμός:
ggplot(data = MSFT, aes(x = factor(Year), y = Close, fill = factor(Year))) +
geom_boxplot(alpha = 0.7) +
labs(
title = "Κατανομή Τιμής Κλεισίματος ανά Έτος",
x = "Έτος",
y = "Τιμή Κλεισίματος",
fill = "Έτος"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Τροποποίηση της εμφάνισης των ετικετών στον άξονα x
# Με γωνία 45 μοιρών για να διβάζονται καλύτεραΣχολιασμός:
ggplot(data = MSFT, aes(x = Close)) +
geom_histogram(binwidth = 10, fill = "purple", color = "#4302d9") +
labs(
title = "Κατανομή Τιμής Κλεισίματος της Μετοχής Microsoft",
x = "Τιμή Κλεισίματος",
y = "Συχνότητα"
) +
theme_minimal()Σχολιασμός:
ggplot(data = MSFT, aes(x = factor(Year), y = Close, fill = factor(Year))) +
stat_summary(fun = mean, geom = "bar") +
labs(
title = "Μέση Τιμή Κλεισίματος της Μετοχής Microsoft ανά Έτος",
x = "Έτος",
y = "Μέση Τιμή Κλεισίματος"
) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none"
# Τροποποίηση της εμφάνισης των ετικετών στον άξονα x
# Με γωνία 45 μοιρών για να διβάζονται καλύτερα
)Σχολιασμός:
(με forward method)
Από το αρχικό dataset (MSFT) θα χρειασιμοποιήσω το 80% για το train του μοντέλου μου και το 20% για το test.
Ξεκινάω προσθέτωντας ως πρώτη μεταβλητή την τιμή ανοίγματος της ημέρας.
##
## Call:
## lm(formula = Close ~ Open, data = train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -15.7517 -0.1567 0.0007 0.1576 22.0107
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.0008382 0.0147936 -0.057 0.955
## Open 1.0002574 0.0002024 4942.064 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.037 on 7264 degrees of freedom
## Multiple R-squared: 0.9997, Adjusted R-squared: 0.9997
## F-statistic: 2.442e+07 on 1 and 7264 DF, p-value: < 2.2e-16
Σχολιασμός:
ggplot(train, aes(x = Open, y = Close)) +
geom_point(alpha = 1, color = "black", size = 1) +
geom_smooth(method = "lm", color = "red", se = TRUE) +
labs(
title = "Μοντέλο 1: Close ~ Open",
subtitle = paste("R² =", round(summary(model1)$r.squared, 4)),
x = "Open ($)",
y = "Close ($)"
) +
theme_minimal()## `geom_smooth()` using formula = 'y ~ x'
Προσθέτω την ενδοημερήσια μεταβλητότητα.
##
## Call:
## lm(formula = Close ~ Open + Daily_Range, data = train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -15.2455 -0.1660 -0.0032 0.1483 22.8888
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.0022931 0.0148094 0.155 0.876950
## Open 1.0012102 0.0003396 2948.434 < 2e-16 ***
## Daily_Range -0.0482596 0.0138176 -3.493 0.000481 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.036 on 7263 degrees of freedom
## Multiple R-squared: 0.9997, Adjusted R-squared: 0.9997
## F-statistic: 1.223e+07 on 2 and 7263 DF, p-value: < 2.2e-16
Σχολιασμός:
Daily_Range είναι θετικός και στατιστικά
σημαντικός, άρα η μεταβλητή συνεισφέρει στο μοντέλο.ggplot(train, aes(x = fitted(model2), y = residuals(model2))) +
geom_point(alpha = 1, color = "purple", size = 1) +
geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
labs(
title = "Μοντέλο 2: Close ~ Open + Daily_Range",
subtitle = paste("R² =", round(summary(model2)$r.squared, 4)),
x = "Fitted Values ($)",
y = "Residuals"
) +
theme_minimal()Προσθέτω και τον όγκο συναλλαγών για να δω αν θα υπάρξει βελτίωση.
##
## Call:
## lm(formula = Close ~ Open + Daily_Range + Volume, data = train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -15.2560 -0.1678 -0.0060 0.1484 22.8659
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.354e-02 2.664e-02 0.508 0.61137
## Open 1.001e+00 3.634e-04 2755.065 < 2e-16 ***
## Daily_Range -4.660e-02 1.420e-02 -3.282 0.00103 **
## Volume -1.695e-10 3.339e-10 -0.508 0.61164
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.036 on 7262 degrees of freedom
## Multiple R-squared: 0.9997, Adjusted R-squared: 0.9997
## F-statistic: 8.153e+06 on 3 and 7262 DF, p-value: < 2.2e-16
Σχολιασμός:
data.frame(
Μοντέλο = c("M1: Open",
"M2: Open + Daily_Range",
"M3: Open + Daily_Range + Volume"),
R2 = c(round(summary(model1)$r.squared, 4),
round(summary(model2)$r.squared, 4),
round(summary(model3)$r.squared, 4)),
Adj_R2 = c(round(summary(model1)$adj.r.squared, 4),
round(summary(model2)$adj.r.squared, 4),
round(summary(model3)$adj.r.squared, 4)),
SSE = c(round(sum(residuals(model1)^2), 0),
round(sum(residuals(model2)^2), 0),
round(sum(residuals(model3)^2), 0))
)## Μοντέλο R2 Adj_R2 SSE
## 1 M1: Open 0.9997 0.9997 7811
## 2 M2: Open + Daily_Range 0.9997 0.9997 7798
## 3 M3: Open + Daily_Range + Volume 0.9997 0.9997 7798
Παίρνω το Μοντέλο 2 που έφτιαξα στο train και το εφαρμόζω στο test set ώστε να παρατηρήσω εάν οι προβλέψεις είναι κοντά στις πραγματικές τιμές.
# Προβλέψεις στο test set
test$Predicted <- predict(model2, newdata = test)
test$Residual <- test$Close - test$Predicted
#το predict() παίρνει τους συντελεστές από το train και τους εφαρμόζει στις τιμές Open και Daily_Range του test για να υπολογίσει προβλεπόμενες τιμές Close# Actual vs Predicted
ggplot(test, aes(x = Close, y = Predicted)) +
geom_point(alpha = 0.5, color = "#CD2990", size = 1) +
geom_abline(intercept = 0, slope = 1, color = "#00CDCD",
linetype = "dashed", linewidth = 1) +
labs(
title = "Πραγματικές vs Προβλεπόμενες Τιμές (Test Set)",
x = "Πραγματική Τιμή Close ($)",
y = "Προβλεπόμενη Τιμή ($)"
) +
theme_minimal()# R² στο test set
ss_res <- sum(test$Residual^2)
ss_tot <- sum((test$Close - mean(test$Close))^2)
r2_test <- 1 - ss_res / ss_tot
cat("Το R² στο Test Set είναι:", round(r2_test, 4))## Το R² στο Test Set είναι: 0.9997
Το dataset της Microsoft περιέχει ημερήσιες τιμές μετοχής από το 1986
έως το 2022. Μόλις κοιτάξουμε τα scatterplots, είναι εμφανές ότι η τιμή
ανοίγματος (Open) και η τιμή κλεισίματος
(Close) κινούνται σχεδόν ταυτόχρονα, κάτι απολύτως λογικό,
αφού μια μετοχή σπάνια κλείνει πολύ μακριά από εκεί που άνοιξε
την ίδια μέρα.
Αυτό επιβεβαιώνεται και από το R² του πρώτου μοντέλου, που είναι εξαιρετικά υψηλό. Στην πράξη αυτό σημαίνει ότι αν ξέρεις την τιμή που άνοιξε η μετοχή σήμερα, μπορείς να προβλέψεις με πολύ μεγάλη ακρίβεια και την τιμή που θα κλείσει.
Με την προσθήκη του dailyRange (η διαφορά υψηλότερης και χαμηλότερης τιμής ημέρας) το R² βελτιώνεται ελαφρά. Αυτό λέει ότι ημέρες με μεγάλη ενδοημερήσια κίνηση τείνουν να έχουν και υψηλότερη τιμή κλεισίματος, κάτι που έχει νόημα, καθώς έντονη κίνηση συνήθως σημαίνει μεγάλο ενδιαφέρον για τη μετοχή.
Όταν προσθέσαμε τον όγκο συναλλαγών (Volume), το R² δεν
άλλαξε ουσιαστικά. Αυτό σημαίνει ότι το πόσες μετοχές αλλάζουν χέρια σε
μια μέρα δεν βοηθά ιδιαίτερα στην πρόβλεψη της τιμής κλεισίματος,
τουλάχιστον όχι πάνω από αυτό που ήδη εξηγούν το Open και
το Daily_Range.
Τελικά, επιλέξαμε το Μοντέλο 2 ως το πιο κατάλληλο καθώς είναι απλό, ερμηνεύσιμο, και το R² του στο test set είναι σχεδόν ίδιο με αυτό του train set. Αυτό σημαίνει ότι δεν απομνημόνευσε τα δεδομένα αλλά έμαθε πραγματικά κάτι που ισχύει και σε νέες παρατηρήσεις.