1 Σχολιασμός/Παρουσίαση του Dataset

Για την ανάλυση επιλέχθηκε το dataset Microsoft Stock Data το οποίο περιλαμβάνει ιστορικά δεδομένα της μετοχής της Microsoft. Περιέχει πληροφορίες για την καθημερινή τιμή της μετοχής στο χρηματιστήριο.

  • Date: η ημερομηνία της συναλλαγής

  • Open: η τιμή της μετοχής όταν άνοιξε το χρηματιστήριο την συγκεκριμένη ημέρα

  • High: η υψηλότερη τιμή που έφτασε η μετοχή μέσα στη μέρα

  • Low: η χαμηλότερη τιμή της μετοχής μέσα στη μέρα

  • Close: η τιμή της μετοχής όταν έκλεισε το χρηματιστήριο

  • Adj Close: τιμή κλεισίματος προσαρμοσμένη ώστε να αντικατοπτρίζει την αξία μετά τον υπολογισμό τυχόν εταιρικών ενεργειών

  • Volume: ο αριθμός των συναλλαγών που πραγματοποιήθηκαν εκείνη την ημέρα

Kaggle: Microsoft Stock Data

# Εισαγωγή του dataset
MSFT <- read.csv("MSFT.csv")

# Μετατροπή της στήλης Date σε ημερομηνία
MSFT$Date <- as.Date(MSFT$Date)

1.1 Δημιουργία Νέας Μεταβλητής

Δημιουργώ μία επιπλέον μεταβλητή:

  • dailyRange: η διαφορά High - Low, που εκφράζει πόσο άλλαξε η μετοχή μέσα στην ημέρα

    (Μέτρο Ενδοημερήσιας Μεταβλητότητας)

    MSFT$Daily_Range <- MSFT$High - MSFT$Low

1.2 Εξέταση Δεδομένων

# εμφάνιση των δεδομένων σε μορφή πίνακα, ώστε να είναι εύκολη η οπτική εξέτασή τους
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
# εμφάνιση της δομής του dataset και τους τύπους δεδομένων των μεταβλητών.
str(MSFT)
## '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 ...

1.3 Οπτικοποίηση Δεδομένων

# Φόρτωση βιβλιοθήκης
library(tidyverse)
## ── 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

1.3.1 Scatterplot

1.3.1.1 Σχέση Τιμής Ανοίγματος και Τιμής Κλεισίματος

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()

  • Σχολιασμός:

    • Το διάγραμμα δείχνει τη σχέση μεταξύ της τιμής ανοίγματος και της τιμής κλεισίματος της μετοχής. Παρατηρείται ότι οι δύο μεταβλητές έχουν ισχυρή συσχέτιση, καθώς όταν η τιμή ανοίγματος είναι υψηλότερη, και η τιμή κλεισίματος είναι υψηλή. Αυτό δείχνει ότι οι ημερήσιες μεταβολές της μετοχής είναι συνήθως μικρές σε σχέση με τη συνολική της αξία.

1.3.2 Boxplot

1.3.2.1 Κατανομή Τιμών Κλεισίματος ανά Έτος

#Δημιουργώ το έτος από την ημερομηνία
MSFT$Year <- format(as.Date(MSFT$Date), "%Y")
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 μοιρών για να διβάζονται καλύτερα
  • Σχολιασμός:

    • Το διάγραμμα δείχνει την κατανομή της τιμής κλεισίματος της μετοχής ανά έτος. Κάθε κουτί στο διάγραμμα δείχνει πώς κατανέμονται οι τιμές της μετοχής μέσα στη συγκεκριμένη χρονιά. Έτσι, μπορούμε να δούμε πώς μεταβάλλεται η τιμή της μετοχής της Microsoft μέσα στα χρόνια και αν υπάρχουν μεγάλες διακυμάνσεις.

1.3.3 Histogram

1.3.3.1 Κατανομή Τιμής Κλεισίματος

ggplot(data = MSFT, aes(x = Close)) +
    geom_histogram(binwidth = 10, fill = "purple", color = "#4302d9") +
    labs(
        title = "Κατανομή Τιμής Κλεισίματος της Μετοχής Microsoft",
        x = "Τιμή Κλεισίματος",
        y = "Συχνότητα"
    ) +
    theme_minimal()

  • Σχολιασμός:

    • Το ιστόγραμμα παρουσιάζει τη συχνότητα εμφάνισης των τιμών κλεισίματος της μετοχής της Microsoft. Μέσα από το διάγραμμα μπορούμε να εντοπίσουμε σε ποια επίπεδα τιμών εμφανίζονται πιο συχνά οι τιμές της μετοχής και να παρατηρήσουμε τη συνολική μορφή της κατανομής.

1.3.4 Bar Chart

1.3.4.1 Μέση Τιμή Κλεισίματος ανά Έτος

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 μοιρών για να διβάζονται καλύτερα
  )

  • Σχολιασμός:

    • Το bar chart παρουσιάζει τη μέση τιμή κλεισίματος της μετοχής της Microsoft για κάθε έτος. Κάθε μπάρα αντιστοιχεί σε ένα έτος και το ύψος της δείχνει τη μέση τιμή κλεισίματος των ημερών του αντίστοιχου έτους. Το διάγραμμα βοηθά στην κατανόηση της γενικής τάσης της μετοχής μέσα στον χρόνο.

2 Δημιουργία Μοντέλων Γραμμικής Παλινδρόμησης

(με forward method)

2.1 Δημιουργία Αρχείων Τrain & Τest

Από το αρχικό dataset (MSFT) θα χρειασιμοποιήσω το 80% για το train του μοντέλου μου και το 20% για το test.

set.seed(42)
n <- nrow(MSFT)
train_idx <- sample(1:n, size = floor(0.8 * n))

train <- MSFT[ train_idx, ]
test <- MSFT[-train_idx, ]

2.2 Μοντέλο 1

Ξεκινάω προσθέτωντας ως πρώτη μεταβλητή την τιμή ανοίγματος της ημέρας.

model1 <- lm(Close ~ Open, data = train)

summary(model1)
## 
## 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
  • Σχολιασμός:

    • Η τιμή ανοίγματος εξηγεί ένα πολύ μεγάλο ποσοστό της διακύμανσης του Close (R² ≈ 0.99), κάτι αναμενόμενο καθώς η μετοχή σπάνια κλείνει πολύ μακριά από την τιμή που άνοιξε.
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'

2.3 Μοντέλο 2

Προσθέτω την ενδοημερήσια μεταβλητότητα.

model2 <- lm(Close ~ Open + Daily_Range, data = train)
summary(model2)
## 
## 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
  • Σχολιασμός:

    • Παρατηρώ ότι το R² βελτιώνεται ελαφρά σε σχέση με το Μοντέλο 1. Ο συντελεστής του 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()

2.4 Μοντέλο 3

Προσθέτω και τον όγκο συναλλαγών για να δω αν θα υπάρξει βελτίωση.

model3 <- lm(Close ~ Open + Daily_Range + Volume, data = train)
summary(model3)
## 
## 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
  • Σχολιασμός:

    • Το R² παραμένει σχεδόν ίδιο με το Μοντέλο 2. Το Volume δεν προσφέρει επιπλέον ερμηνευτική ικανότητα, οπότε θα επιλέξω το απλούστερο μοντέλο καθώς τα αποτελέσματα είναι ισοδύναμα, άρα το Μοντέλο 2 ως τελικό.

2.4.1 Συνολική Εικόνα

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

3 Εφαρμογή/Πρόβλεψη σε Νέα Δεδομένα (Test)

Παίρνω το Μοντέλο 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

4 Συμπεράσματα

Το dataset της Microsoft περιέχει ημερήσιες τιμές μετοχής από το 1986 έως το 2022. Μόλις κοιτάξουμε τα scatterplots, είναι εμφανές ότι η τιμή ανοίγματος (Open) και η τιμή κλεισίματος (Close) κινούνται σχεδόν ταυτόχρονα, κάτι απολύτως λογικό, αφού μια μετοχή σπάνια κλείνει πολύ μακριά από εκεί που άνοιξε την ίδια μέρα.

Αυτό επιβεβαιώνεται και από το του πρώτου μοντέλου, που είναι εξαιρετικά υψηλό. Στην πράξη αυτό σημαίνει ότι αν ξέρεις την τιμή που άνοιξε η μετοχή σήμερα, μπορείς να προβλέψεις με πολύ μεγάλη ακρίβεια και την τιμή που θα κλείσει.

Με την προσθήκη του dailyRange (η διαφορά υψηλότερης και χαμηλότερης τιμής ημέρας) το R² βελτιώνεται ελαφρά. Αυτό λέει ότι ημέρες με μεγάλη ενδοημερήσια κίνηση τείνουν να έχουν και υψηλότερη τιμή κλεισίματος, κάτι που έχει νόημα, καθώς έντονη κίνηση συνήθως σημαίνει μεγάλο ενδιαφέρον για τη μετοχή.

Όταν προσθέσαμε τον όγκο συναλλαγών (Volume), το R² δεν άλλαξε ουσιαστικά. Αυτό σημαίνει ότι το πόσες μετοχές αλλάζουν χέρια σε μια μέρα δεν βοηθά ιδιαίτερα στην πρόβλεψη της τιμής κλεισίματος, τουλάχιστον όχι πάνω από αυτό που ήδη εξηγούν το Open και το Daily_Range.

Τελικά, επιλέξαμε το Μοντέλο 2 ως το πιο κατάλληλο καθώς είναι απλό, ερμηνεύσιμο, και το R² του στο test set είναι σχεδόν ίδιο με αυτό του train set. Αυτό σημαίνει ότι δεν απομνημόνευσε τα δεδομένα αλλά έμαθε πραγματικά κάτι που ισχύει και σε νέες παρατηρήσεις.