Σε αυτές τις σύντομες σημειώσεις θα καλύψουμε τις βασικές εισαγωγικές έννοιες του λογισμικού R:

1 RStudio

1.1 R Scripts

Οι εντολές που δίνονται απευθείας στο παράθυρο Console δεν αποθηκεύονται κάπου συστηματικά ώστε να μπορούν να επαναληφθούν εύκολα (ανακτώνται με τον cursor up/down). Τα R scripts δίνουν τη δυνατότητα να αποθηκεύονται εντολές/συναρτήσεις κλπ σε αρχείο εντολών (με κατάληξη .R) και να εκτελείται μέρος ή όλο το αρχείο εύκολα.

  • Ctrl-Enter : Εκτέλεση τρέχουσας γραμμής
  • Ctrl-Alt-R: Εκτέλεση όλου του αρχείου
  • Ctrl-Shift-S : Ισοδύναμο με την εντολή source(“filename”), εκτέλεση όλου του αρχείου (με κάποιες διαφορές στη συμπεριφορά σχετικά με την Ctrl-Alt-R)

1.2 Φάκελος Εργασίας

Σε όλες τις εντολές αποθήκευσης και ανάκτησης δεδομένων που θα δούμε στη συνέχεια, το πρόγραμμα ψάχνει στον τρέχοντα φάκελο εργασίας. Ο default φάκελος εξαρτάται από το λειτουργικό σύστημα και τη συγκεκριμένη εγκατάσταση του R. Ο τρέχων φάκελος εργασίας μπορεί να βρεθεί με την εντολή getwd().

Γενικά είναι καλή πρακτική τα αρχεία που αναφέρονται σε διαφορετικά projects να συγκεντώνονται σε χωριστούς φακέλους για την καλύτερη οργάνωση και αναπαραξιμότητα των εργασιών. Για να μπορεί το πρόγραμμα να βρίσκει τα αρχεία που χρειάζονται σε μια ανάλυση, ο τρέχων φάκελος εργασίας μπορεί να αλλάξει με την εντολή setwd(“path”):

Το path του φακέλου πρέπει να δοθεί με “/” και όχι με “" που είναι το default των Windows. Επίσης στα Windows το path ενός φακέλου μπορεί να βρεθεί ανοίγοντας το φάκελο στο Windows Explorer και πατώντας στο παράθυρο πάνω από τον κατάλογο των αρχείων. Σε άλλα λειτουργικά συστήματα το path μπορεί να βρεθεί με ανάλογο τρόπο και συνήθως δίνεται με”/“.

Επομένως η τυπική ροή εργασίας για είναι η εξής: (α) Επιλέγουμε ή δημιουργούμε ένα φάκελο στον οποίο τοποθετούμε τα δεδομένα και τα αρχεία που θα χρειαστούν στο project που εργαζόμαστε, (β) Βρίσκουμε το path του φακέλου με τον παραπάνω τρόπο, (γ) Στο παράθυρο εντολών του R δίνουμε την εντολή setwd(path), όπου στο path αν χρειάζεται έχουμε αντικασταστήσει τα “\” με “/” (ή με διπλό “\\”) (δ) Όποια R scripts ή άλλα αρχεία δημιουργήσουμε στο παράθυρο των scripts τα αποθηκεύουμε στον τρέχοντα φάκελο για να είναι προσβάσιμα σε επόμενες κλήσεις.

1.3 R Markdown για Δυναμικά Κείμενα, Τεκμηρίωση και Αναπαραξιμότητα Κώδικα

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

Ανεξάρτητα από τη συγγραφή εργασιών, όταν για μια ανάλυση γράφονται μεγάλα τμήματα κώδικα, είναι σημαντικό να γίνεται επαρκής τεκμηρίωση (documentation) των προγραμμάτων για δύο βασικούς λόγους. Πρώτον, αν σε μεταγενέστερο χρόνο χρειαστεί να επαναληφθεί η ανάλυση ή να γίνουν κάποιες τροποποιήσεις στον κώδικα, είναι πολύ δύσκολο και χρονοβόρο να γίνει αυτό αν δεν υπάρχει τεκμηρίωση με επεξηγήσεις και οδηγίες για την εκτέλεση. Δεύτερο, ιδιαίτερα σε μεγάλες και πολύπλοκες μελέτες που υποβάλλονται για δημοσίευση σε επιστημονικά περιοδικά (και όχι μόνο), είναι απαραραίτητο να εξασφαλίζεται η αναπαραξιμότητα (reproducibility) των αναλύσεων που έχουν διεξαχθεί. Αυτό σημαίνει ότι ένας τρίτος θα πρέπει να μπορεί, αν έχει στη διάθεσή του τα προγράμματα και τα δεδομένα που έχουν χρησιμοποιηθεί, να αναπαραγάγει τα αποτελέσματα που έχουν δημοσιευθεί ή έχουν υποβληθεί για δημοσίευση. Για το λόγο πολλά περιοδικά δίνουν τη δυνατότητα στους συγγραφείς να αναρτούν τα προγράμματα που έχουν χρησιμοποιήσει σε κατάλληλα αποθετήρια για χρήση από την επιστημονική κοινότητα. Σε αυτή την περίπτωση είναι σημαντικό τα προγράμματα που αναρτώνται να είναι πλήρως τεκμηριωμένα και αναπαράξιμα. Για την τεκμηρίωση ενός προγράμματος ο πιο συνηθισμένος τρόπος είναι να γράφονται αναλυτικά σχόλια μέσα στα προγράμματα με επεξηγήσεις και οδηγίες για την εκτέλεση.

Ένας πολύ χρήσιμος και αποτελεσματικός μηχανισμός και για τα δύο παραπάνω θέματα είναι η διαδικασία R Markdown.

Το R Markdown είναι ένας τύπος αρχείου κειμένου με σχετικά απλές εντολές μορφοποίησης (βασισμένες στο γενικότερο πρότυπο Markdown) που συγγραφή δυναμικού κειμένου. Αυτό σημαίνει ότι στο κείμενο περιλαμβάνονται και τμήματα με εντολές στη γλώσσα R (ή και σε άλλες γλώσσες προγραμματισμού όπως η python). Όποτε αυτό το κείμενο “εκτελείται” από το Rstudio, τα τμήματα του κώδικα εκτελούνται και τα αποτελέσματα ενσωματώνονται μέσα στο κείμενο. Αυτό σημαίνει ότι αλλαγές είτε στο συνοδευτικό κείμενο της εργασίας είτε στους αντίστοιχους κώδικες γίνονται ενιαία στο ίδιο αρχείο Rmarkdown χωρίς να χρειάζεται κάθε φορά μεταφορά και επικόλληση των αποτελεσμάτων από το R στον αντίστοιχο επεξεργαστή κειμένου.

Για μια εισαγωγή στη γλώσα RMarkdown δείτε https://rmarkdown.rstudio.com/articles_intro.html

Μια σύντομη περίληψη των εντολών RMarkdown https://rstudio.com/wp-content/uploads/2016/03/rmarkdown-cheatsheet-2.0.pdf?_ga=2.48070951.1440147942.1611558223-216792143.1611045836

Σύντομος οδηγός https://rstudio.com/wp-content/uploads/2015/03/rmarkdown-reference.pdf?_ga=2.77958804.1440147942.1611558223-216792143.1611045836

2 R Objects

Τα objects είναι γενικά μεταβλητές στις οποίες αποθηκεύονται δεδομένα διαφόρων τύπων (αριθμοί, χαρακτήρες, διανύσματα, πίνακες κλπ). Η γενική εντολή ανάθεσης (assignment) είναι <- (το = είναι ισοδύναμο στις περισσότερες περιπτώσεις). Μετά από μια εντολή ανάθεσης η μεταβλητή (και μέρος του περιεχομένου της) φαίνονται στο παράθυρο Environment. Γράφοντας μόνο το όνομα της μεταβλητής επιστρέφεται το περιεχόμενο. Τα ονόματα των objects είναι case sensitive. Νέα ανάθεση στην ίδια μεταβλητή αποθηκεύει τη νέα τιμή ενώ η παλιά χάνεται.

a <- 2+4
a
## [1] 6
a <- 15
a
## [1] 15
A <- 6
A
## [1] 6
a
## [1] 15
rm(a)

2.1 Χρήσιμες κατηγορίες objects (data classes)

  • Πραγματικοί Αριθμοί: double (“διπλής ακρίβειας”)
  • Ακέραιοι Αριθμοί: integer
  • Αριθμητικά δεδομένα: numeric (double/integer)
  • Λογικοί : logical (0 = FALSE, άλλο=TRUE)
  • Χαρακτήρες : character

Έλεγχος κατηγορίας : is.double, is.integer, κλπ.

Μετατροπή από μια κατηγορία σε άλλη : as.double, as.integer, κλπ.

Μετατροπή double σε integer γίνεται και μέσω των συναρτήσεων floor (ακέραιο μέρος) και round (στρογγύλευση), οι οποίες όμως δεν αλλάζουν τον τύπο της μεταβλητής.

a=5.8
a
## [1] 5.8
is.integer(a)
## [1] FALSE
is.double(a)
## [1] TRUE
is.numeric(a)
## [1] TRUE
is.logical(a)
## [1] FALSE
# Αλλαγή τύπου 
b=as.integer(a)
b
## [1] 5
is.integer(b)
## [1] TRUE
is.double(b)
## [1] FALSE
is.numeric(b)
## [1] TRUE
# Υπολογισμός ακέραιου μέρους και στρογγύλευσης
c=floor(a)
c
## [1] 5
is.integer(c)
## [1] FALSE
is.double(c)
## [1] TRUE
d=round(a)
d
## [1] 6
is.integer(d)
## [1] FALSE
is.double(d)
## [1] TRUE
# Μετατροπή σε logical
a=4
is.double(a)
## [1] TRUE
b=as.logical(a)
b
## [1] TRUE
is.logical(b)
## [1] TRUE
c=as.integer(b)
c
## [1] 1
a=0
b=as.logical(a)
b
## [1] FALSE
c=as.integer(b)
c
## [1] 0
#Character
a="hello"
a
## [1] "hello"
is.character(a)
## [1] TRUE
is.numeric(a)
## [1] FALSE

3 Πράξεις

3.1 Αριθμητικές Πράξεις

2+2
## [1] 4
2*2
## [1] 4
2^3
## [1] 8

3.2 Λογικές Πράξεις

2<3
## [1] TRUE
2 >=5
## [1] FALSE
2==2 #Προσοχή! = : ανάθεση, == : έλεγχος ισότητας
## [1] TRUE
# Άρνηση : !

2 > 4 
## [1] FALSE
!(2>4)
## [1] TRUE
2<4
## [1] TRUE
!(2<4)
## [1] FALSE
# Σύζευξη : & 

2<3 & 2<4
## [1] TRUE
2<3 & 2<1
## [1] FALSE
# & μεταξύ αριθμών : οι αριθμοί θεωρούνται logical : 0=F, διαφορετικά=Τ

1&2
## [1] TRUE
3&4
## [1] TRUE
3&0
## [1] FALSE
#Προσοχή στις παρενθέσεις !

(4 < 5 ) & 1
## [1] TRUE
4 < (5 & 1)
## [1] FALSE

4 Δομές Δεδομένων (data structures)

4.1 Διάνυσμα (vector)

Μονοδιάστατη δομή, περιέχει στοιχεία ίδιου τύπου. Δημιουργία μέσω συνάρτησης combine: c().

Αριθμός στοιχείων διανύσματος: length()

v=c(1,2,3,4,5)
v
## [1] 1 2 3 4 5
is.vector(v)
## [1] TRUE
v=c(1:10)
v
##  [1]  1  2  3  4  5  6  7  8  9 10
# Δημιουργία διανύσματος με επαναλαμβανόμενα στοιχεία 
w=rep(10, 4)
w
## [1] 10 10 10 10
#Συμπλήρωση με επιπλέον στοιχεία
v=c(v,15)
v
##  [1]  1  2  3  4  5  6  7  8  9 10 15
w=c("Mon", "Tue", "Wed")
w
## [1] "Mon" "Tue" "Wed"
is.vector(w)
## [1] TRUE
length(w)
## [1] 3
w=c(w,2)
w
## [1] "Mon" "Tue" "Wed" "2"
# Προσοχή: Εδώ το 2 προστέθηκε ως character για να συμφωνεί με τα υπόλοιπα στοιχεία του w. 

Πλοήγηση (εύρεση στοιχείων) διανύσματος : [ ]

v=c(10,20,30,40,50, 60,70)
v
## [1] 10 20 30 40 50 60 70
v[2]
## [1] 20
v[c(2,4)]
## [1] 20 40
v[2:4]
## [1] 20 30 40
v[-2] # εκτός του δεύτερου στοιχείου
## [1] 10 30 40 50 60 70
v[-c(2,4)]
## [1] 10 30 50 60 70
v[-(2:4)]
## [1] 10 50 60 70

4.2 List

Μονοδιάστατη γενική δομή, περιέχει στοιχεία διαφορετικών γενικά τύπων (μπορεί και λίστες). Δημιουργείται με τη συνάρτηση list()

L=list(1,2,"hello",FALSE)

L
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2
## 
## [[3]]
## [1] "hello"
## 
## [[4]]
## [1] FALSE
L[1]
## [[1]]
## [1] 1
is.numeric(L[1])
## [1] FALSE
is.logical(L[4])
## [1] FALSE
# Ένα στοιχείο της λίστας μπορεί να είναι το ίδιο άλλη λίστα

L=list(1,2,"hello", list(3,"a"))

Η λίστα είναι πολύ χρήσιμη δομή για την συγκεντρωτική αποθήκευση δεδομενων διαφορετικών τύπων. Χρησιμοποιείται συχνά για την αποθήκευση των αποτελεσμάτων μιας συνάρτησης που δίνει πολλαπλά αποτελέσματα (βλ. παρακάτω). Ένα πολύ βολικό χαρακτηριστικό της δομής λίστας είναι ότι μπορούν να δοθούν ονόματα στα διάφορα στοιχεία της και μετά αυτά να καλούνται άμεσα και όχι με τον αριθμό του στοιχείου. Για παράδειγμα έστω ότι στη μεταβλητή a αποθηκεύεται μια λίστα που περιέχει στοιχεία για ένα άτομο, δηλαδή το όνομα (δεδομένα τύπου character), την ηλικία (δεδομένο τύπου double) και το φύλο (δεδομένο τύπου factor με επίπεδα m/f). Αν η ανάθεση γίνει με την εντολή

a=list("Tom", 25, "m")

τότε για να ανακτήσουμε την ηλικία του ατόμου δίνουμε

a[[2]]
## [1] 25

Εναλλακτικά η ανάθεση μπορεί να γίνει δίνοντας ονόματα στα στοιχεία της λίστας:

a=list(name="Tom", age=25, sex="m")

Τώρα για να ανακτήσουμε την ηλικία του ατόμου μπορούμε άμεσα να δώσουμε την εντολή

a$age
## [1] 25

4.3 Πίνακας (Matrix)

Διδιάστατη δομή (γραμμές και στήλες), όλα τα στοιχεία του ίδιου τύπου.

Δημιουργία Πίνακα

1. Μέσω εντολών rbind, cbind - rbind: ενώνει διανύσματα γραμμής κάθετα - сbind: ενώνει διανύσματα στήλης οριζόντια

rbind(c(1,2), c(3,4))
##      [,1] [,2]
## [1,]    1    2
## [2,]    3    4
cbind(c(1,2), c(3,4))
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4

2. Μέ μετατροπή διανύσματος σε πίνακα μέσω συνάρτησης matrix

v=c(1:6)

# Kατά γραμμή πρώτα
m1=matrix(v,nrow=2,byrow=T)
m1
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6
# Kατά στήλη πρώτα
m2=matrix(v,nrow=2,byrow=F)
m2
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6

Επιλογή στοιχείων πίνακα

Αντίστοιχα με τα διανύσματα, τώρα σε δύο διαστάσεις

m1=matrix(c(1:24),nrow=6,byrow = T)

m1
##      [,1] [,2] [,3] [,4]
## [1,]    1    2    3    4
## [2,]    5    6    7    8
## [3,]    9   10   11   12
## [4,]   13   14   15   16
## [5,]   17   18   19   20
## [6,]   21   22   23   24
m1[2,3]
## [1] 7
#Κενό σε μια διάσταση σημαίνει επιλογή όλων των γραμμών ή στηλών

#2η γραμμή όλες οι στήλες
m1[2,]
## [1] 5 6 7 8
#1η στήλη, όλες οι γραμμές
m1[,1]
## [1]  1  5  9 13 17 21
# πρώτες 2 γραμμές, στήλες 2 και 4
m1[1:2, c(2,4)]
##      [,1] [,2]
## [1,]    2    4
## [2,]    6    8

** Επιλογή με λογικά κριτήρια **

Η επιλογή γραμμών ή στηλών μπορεί να γίνει μέσω λογικών διανυσμάτων όπου T σημαίνει ότι η αντίστοιχη γραμμή/στήλη επιλέγεται και F ότι δεν επιλέγεται.

m1
##      [,1] [,2] [,3] [,4]
## [1,]    1    2    3    4
## [2,]    5    6    7    8
## [3,]    9   10   11   12
## [4,]   13   14   15   16
## [5,]   17   18   19   20
## [6,]   21   22   23   24
# Επιλογή γραμμών 1 και 3

m1[c(T,F,T,F,F,F),]
##      [,1] [,2] [,3] [,4]
## [1,]    1    2    3    4
## [2,]    9   10   11   12

Το λογικό διάνυσμα επιλογής μπορεί να έχει δημιουργηθεί από ελέγχους πάνω στα ίδια τα στοιχεία του διανύσματος ή του πίνακα.

v=c(1:10)
v
##  [1]  1  2  3  4  5  6  7  8  9 10
#Έλεγχος των στοιχείων του v αν είναι >=4
v>=4
##  [1] FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
#Επιλογή των στοιχείων του v τα οποία είναι >=4
v[v>=4]
## [1]  4  5  6  7  8  9 10
#Λειτουργεί και με διαφορετικά διανύσματα
w=c(11:20)

v
##  [1]  1  2  3  4  5  6  7  8  9 10
w
##  [1] 11 12 13 14 15 16 17 18 19 20
w[v>=4]
## [1] 14 15 16 17 18 19 20
# Αντίστοιχα ισχύουν για την επιλογή γραμμών ή στηλών πίνακα

m1
##      [,1] [,2] [,3] [,4]
## [1,]    1    2    3    4
## [2,]    5    6    7    8
## [3,]    9   10   11   12
## [4,]   13   14   15   16
## [5,]   17   18   19   20
## [6,]   21   22   23   24
# Έλεγχος των στοιχείων της 2ης στήλης που είναι >= 12
m1[,2]>=12
## [1] FALSE FALSE FALSE  TRUE  TRUE  TRUE
# Επιλογή ολόκληρων των γραμμών για τις οποίες τα στοιχεί της 2ης στήλης είναι >= 12
m1[m1[,2]>=12,]
##      [,1] [,2] [,3] [,4]
## [1,]   13   14   15   16
## [2,]   17   18   19   20
## [3,]   21   22   23   24

4.4 Data Frame

Είναι η πιο χρήσιμη δομή δεδομένων στο R.

Διδιάστατη δομή (γραμμές - στήλες)

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

a=c(1:5)

b=c('Mon','Tue','Mon','Wed','Wed')

c=c(T,T,F,T,F)

df=data.frame(a,b,c)
df

Οι στήλες μπορεί να έχουν ονόματα. Στο παραπάνω παράδειγμα “κληρονόμησαν” τα ονόματα των διανυσμάτων από τα οποία προήλθε το data.frame.

Τα ονόματα των στηλών μπορεί να αναζητηθούν και να αλλαχθούν με τη συνάρτηση names.

names(df) #Επιστρέφει διάνυσμα με τα ονόματα των στηλών του df
## [1] "a" "b" "c"
# Αλλαγή του πρώτου ονόματος σε "id_number"
names(df)[1]='id_number'
names(df)
## [1] "id_number" "b"         "c"
# Αλλαγή όλων των ονομάτων
names(df)=c('id_number', 'weekday', 'rain')
names(df)
## [1] "id_number" "weekday"   "rain"
df

Επιλογή στοιχείων data.frame

Μπορεί να γίνει όπως και στους πίνακες με επιλογές γραμμών/στηλών

df[1,2]
## [1] "Mon"
df[c(1:3),2]
## [1] "Mon" "Tue" "Mon"

Μια στήλη επίσης μπορεί να επιλεγεί με το όνομα.

names(df)
## [1] "id_number" "weekday"   "rain"
#Επιλογή 1ης στήλης με αριθμό
df[,1]
## [1] 1 2 3 4 5
#Επιλογή 1ης στήλης με όνομα
df$id_number
## [1] 1 2 3 4 5
#Επιλογή ολόκληρων των γραμμών που έχουν rain=TRUE
df[df$rain==T, ]
#Επιλογή id_number και weekday για τις γραμμές που έχουν rain=TRUE
df[df$rain==T, c(1:2) ]

4.5 Αποθήκευση και Ανάκτηση Δεδομένων

Οποιαδήποτε αντικείμενα έχουν οριστεί σε ένα R session μπορούν να αποθηκευτούν σε ένα αρχείο για μελλοντική ανάκτηση και χρήση, με την εντολή save. Η εντολή αποθηκεύει τα αντικείμενα σε ένα αρχείο με επέκταση “.Rdata”, του οποίο το όνομα δίνεται στην επιλογή file:

a1=5
a2=c(4,3,2)
a3=matrix(runif(10), 2,5)
save(a1, a2, a3, file="a.Rdata")

Το αρχείο τοποθετείται στον τρέχοντα φάκελο εργασίας. Ο φάκελος εργασίας φαίνεται με την εντολή getwd() ενώ διαφορετικός φάκελος εργασίας ορίζεται με την εντολή setwd(“directoryname”).

Η εντολή load(“filename.Rdata”) διαβάζει το αρχείο filename.Rdata και ορίζει εκ νέου όλα τα αντικείμενα που έχουν αποθηκευτεί σε αυτό με τις τιμές τους. Αν υπάρχουν ήδη αντικείμενα με το ίδιο όνομα αυτά αντικαθίστανται από τα αντίστοιχα ανακτημένα.

rm(a1)
a2=4
a2
## [1] 4
load("a.Rdata")
a1
## [1] 5
a2
## [1] 4 3 2

Η εντολή save.image() αποθηκεύει όλα τα αντικείμενα που έχουν οριστεί στο χώρο εργασίας σε ένα αρχείο με όνομα “.Rdata” που τοποθετείται στον τρέχοντα φάκελο εργασίας.

Εκτός από την αποθήκευση και ανάκτηση ήδη ορισμένων αντικειμένων, συχνά χρειάζεται η εισαγωγή δεδομένων από εξωτερικά αρχεία άλλου τύπου (π.χ. κειμένου, Excel κλπ) σε dataframes στο R. Αυτό γίνεται με εντολές της κατηγορίας read (π.χ. read.table(), read.csv() κλπ).

Η πιο συνηθισμένη εντολή αυτής της κατηγορίας είναι η read.csv που διαβάζει ένα αρχείο δεδομένων σε μορφή κειμένου και το μεταφέρει σε ένα dataframe. Κάθε γραμμή του αρχείου αποθηκεύεται σε μια γραμμή του dataframe. Οι στήλες μέσα στο αρχείο κειμένου διαχωρίζονται με ένα συγκεκριμένο χαρακτήρα (π.χ. κόμμα, space, tab, κλπ). Τα ονόματα των στηλών ανακτώνται (αν είναι επιθυμητό) από την πρώτη γραμμή του αρχείου και πρέπει επίσης να είναι διαχωρισμένα με τον ίδιο χαρακτήρα. Ο τύπος των δεδομένων για κάθε στήλη αναγνωρίζεται αυτόματα (αν είναι ο ίδιος σε όλες τις γραμμές). Ο χαρακτήρας που διαχωρίζει τις στήλες ορίζεται με την επιλογή sep=“,” για κόμμα, sep=“ για tabs. Με την επιλογή header=TRUE/FALSE ορίζεται αν η πρώτη γραμμή του αρχείου θα χρησιμοποιηθεί για ονόματα στηλών ή είναι η πρώτη γραμμή των δεδομένων, αντίστοιχα.

ozone=read.csv(file="ozone.data", sep="\t", header=T)
names(ozone)
## [1] "ozone"       "radiation"   "temperature" "wind"

Για την εισαγωγή αρχείων με την εντολή read.csv το εξωτερικό αρχείο πρέπει να είναι μορφής απλού κειμένου με τις προδιαγραφές που αναφέρθηκαν παραπάνω. Αρχεία δεδομένων από άλλα λογισμικά συνήθως αποθηκεύονται σε μορφή συμβατή με το αντίστοιχο λογισμικό (π.χ. .xlsl για αρχεία Excel, .sav για αρχεία SPSS κλπ.). Για να μπορέσουν να εισαχθούν στο R με την εντολή read.csv θα πρέπει πρώτα να αποθηκευτούν σε μορφή κειμένου με κατάλληλες εντολές από το άλλο λογισμικό (π.χ. save as csv από το Excel). Εναλλακτικά μπορούν να χρησιμοποιηθούν άλλες εντολές τύπου read για απευθείας ανάκτηση δεδομένων συγκεκριμένου τύπου.

5 Αποθήκευση Εντολών - Scripts and Functions

Όπως είδαμε παραπάνω σε ένα R script αποθηκεύονται εντολές που μπορούν να εκτελεστούν όλες μαζί σε επόμενη στιγμή. Ένα script μπορεί να εκτελεστεί είτε μέσα από το παράθυρό του (με Ctrl-Enter ανά γραμμή ή Ctrl-Alt-R όλο) είτε από το παράθυρο εντολών του Rstudio (Console) με την εντολή source(“name.R”), όπου name.R είναι το όνομα με το οποίο έχει αποθηκευτεί το script στο φάκελο εργασίας. Ο ενεργός φάκελος εργασίας βρίσκεται με τη συνάρτηση getwd(), και αλλάζει με τη συνάρτηση setwd(“folder path”).

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

Για παράδειγμα, αν το script με όνομα test.R περιέχει τις εξής εντολές

x=2
y=x^3
y
## [1] 8

τότε κάθε φορά που δίνεται η εντολή source(“test.R”), η μεταβλητή x θα παίρνει την τιμή 2 και η μεταβλητή y την τιμή 8. Αν ο χρήστης θέλει να υπολογίσει τον κύβο του 4, θα πρέπει μέσα στο αρχείο test.R να αλλάξει την εντολή x=2 σε x=4, να αποθηκεύσει το αρχείο test.R και να το ξανακαλέσει.

Το εργαλείο της συνάρτησης δίνει ένα πιο ευέλικτο τρόπο εκτέλεσης εντολών. Δημιουργώντας μια συνάρτηση ο χρήστης ορίζει κάποιες μεταβλητές οι οποίες παίρνουν τιμές κατά τη στιγμή κλήσης της συνάρτησης, και επομένως μπορεί να είναι διαφορετικές κάθε φορά που καλείται η συνάρτηση, χωρίς να χρειάζεται επαναπρογραμματισμός και αποθήκευση.

Για παράδειγμα, με τις παρακάτω εντολές δημιουργείται η συνάρτηση cube, δηλαδή ένα νέο object, τύπου συνάρτησης, που υπολογίζει και επιστρέφει τον κύβο του αριθμού x, η τιμή του οποίου δίνεται από τον χρήστη κάθε φορά που καλείται η συνάρτηση. H x είναι το όρισμα (input) και η y το αποτέλεσμα (output) της συνάρτησης cube.

cube = function(x)
{
  y=x^3
  return(y)
}

Όλες οι εντολές που εκτελούνται κάθε φορά που καλείται η συνάρτηση περιλαμβάνονται μέσα σε {}.

Η συνάρτηση cube αποτελεί ένα νέο object που καλείται όπως όλες οι υπόλοιπες προκαθορισμένες συναρτήσεις του R. Τα ονόματα των μεταβλητών x,y θεωρούνται τοπικές μεταβλητές, δηλαδή οι τιμές τους ισχύουν μόνο όσο εκτελείται η συνάρτηση και δεν έχουν σχέση με άλλες μεταβλητές που μπορεί να έχουν οριστεί με το ίδιο όνομα στο εξωτερικό περιβάλλον εργασίας. Επίσης κάθε φορά που καλείται η συνάρτηση cube, αυτές μηδενίζονται και ορίζονται εκ νέου μέσα στη συνάρτηση (δεν διατηρούνται οι τιμές τους από προηγούμενες κλήσεις). Τέλος, τα ονόματα x,y είναι τυπικά, δηλαδή όταν καλείται η συνάρτηση στη θέση του x μπορεί να μπει οποιοσδήποτε αριθμός ή οποιαδήποτε μεταβλητή έχει ήδη οριστεί και θέλουμε να υπολογίσουμε τον κύβο της, και το αποτέλεσμα μπορούμε να το αποθηκεύσουμε σε όποια μεταβλητή θέλουμε και όχι αναγκαστικά στην y.

a=cube(2)
a
## [1] 8
b=cube(5)
b
## [1] 125
x=2
y=cube(x)
y
## [1] 8
d=4
e=cube(d)
e
## [1] 64

Μια συνάρτηση μπορεί να έχει περισσότερα από ένα ορίσματα οποιουδήποτε τύπου (π.χ. αριθμό, χαρακτήρα, διάνυσμα κλπ) αλλά μόνο ένα αποτέλεσμα. Φυσικά αν χρειάζεται, το αποτέλεσμα μπορεί να είναι ένα διάνυσμα ή γενικότερα μια λίστα που περιλαμβάνει περισσότερα αποτελέσματα που προέρχονται από την εκτέλεση της συνάρτησης. Για παράδειγμα συνάρτηση circletest παίρνει ως ορίσματα τους αριθμούς (x,y,r), υπολογίζει την απόσταση του σημείου \((x,y)\) από την αρχή των αξόνων, δηλαδή την ποσότητα \(d=\sqrt{x^2+y^2}\), ελέγχει αν το σημείο βρίσκεται μέσα στον κύκλο με κέντρο την αρχή των αξόνων και ακτίνα \(r\), δηλαδή αν \(d\leq r\) και αποθηκεύει το αποτέλεσμα (TRUE/FALSE) στη λογική μεταβλητή incircle. Το αποτέλεσμα της συνάρτησης είναι μια λίστα που περιέχει τα d, incircle. Επειδή τα d, incircle είναι διαφορετικού τύπου, το αποτέλεσμα πρέπει να είναι λίστα και όχι π.χ. διάνυσμα που απαιτεί όλα τα στοιχεία να είναι του ίδιου τύπου.

circletest = function(x,y,r)
{
  d=sqrt(x^2+y^2)
  incircle=(d <= r)
  result=list(d,incircle)
  return(result)
}

#Κλήση της circletest
z=circletest(2,3,4)
z
## [[1]]
## [1] 3.605551
## 
## [[2]]
## [1] TRUE

6 Βασικά στοιχεία προγραμματισμού

Πολλές λειτουργίες/εργασίες στο R απαιτούν προγραμματισμό πέρα από τις απλές αναθέσεις τιμών σε μεταβλητές. Παρακάτω δίνουμε μερικά παραδείγματα από 3 βασικές λειτουργίες: for, if, while. Αυτές μπορούν να ενσωματωθούν είτε σε script files, είτε σε συναρτήσεις, σύμφωνα με όσα έχουμε πει παραπάνω.

6.1 Λειτουργία for

Με την εντολή for μπορεί να προγραμματιστεί μια λειτουργία που πρέπει να επαναληφθεί συγκεκριμένο αριθμό φορών, κάθε φορά με διαφορετική τιμή μιας μεταβλητής.

Για παράδειγμα, έστω ότι θέλουμε να κατασκευάσουμε ένα διάνυσμα y διάστασης 10, όπου το στοιχείο i είναι ίσο με \(y_i = 3 i^2 - 1\). Αυτό γίνεται με τις παρακάτω εντολές:

y=rep(0,10) #Πρώτα πρέπει να οριστεί το διάνυσμα για να μπορούν να αλλάξουν μετά οι τιμές των στοιχείων του. 
for (i in 1:10)
{
  y[i]=3*i^2 - 1
}
y
##  [1]   2  11  26  47  74 107 146 191 242 299

Σε ένα δεύτερο παράδειγμα, έστω ότι θέλουμε να υπολογίσουμε το άθροισμα \(\sum_{i=1}^10 \log(i+1)\).

S=0 # Ορίζουμε μια μεταβλητή S στην οποία θα προστίθεται η ποσότητα \log(i+1) σε κάθε επανάληψη του for.
for (i in 1:10)
{
  S=S+log(i+1)
}
S
## [1] 17.50231

6.2 Λειτουργία if, if else

Με την εντολή if μπορεί να προγραμματιστεί μια λειτουργία που πραγματοποιείται μόνο αν ικανοποιείται μια συνθήκη Με το συνδυασμό if.. else.. μπορούν να προγραμματιστούν 2 λειτουργίες, έτσι ώστε αν ισχύει μια συνθήκη γίνεται η πρώτη λειτουργία, ενώ αν δεν ισχύει γίνεται η δεύτερη.

x=2

x
## [1] 2
if (2==2) {x=4}

x
## [1] 4
if (2<1) {x=5}

x
## [1] 4
a=3

if (a<1) {x=1} else {x=3}

x
## [1] 3

Με διαδοχικά else/if μπορεί προγραμματιστούν περισσότερες από μια περιπτώσεις. Για παράδειγμα έστω μια συνάρτηση cases, η οποία παίρνει όρισμα έναν αριθμό x και επιστρέφει το \[ y = \left\{ \begin{array}{ll} 1,&\mbox{αν\ } x<0 \\ 2,&\mbox{αν\ } 0 \leq x < 4 \\ 3,&\mbox{αν\ } 4 \leq x < 8 \\ 4,&\mbox{αν\ } x \geq 0 \end{array} \right . \] Η συνάρτηση αυτή μπορεί να γραφεί ως εξής:

cases = function(a)
{
  if (a<0) 
  {
    y=1
  } else if (a<4)
  {
    y=2
  } else if (a<8) 
  {
    y=3
  }
  else 
  {
     y=4
  }
  
  return(y)
}

cases(-2)
## [1] 1
cases(3)
## [1] 2
cases(5)
## [1] 3
cases(20)
## [1] 4

6.3 Λειτουργία while

Η εντολή while συνδυάζει τις for και if: Μια λειτουργία ή σειρά λειτουργιών εκτελείται επαναλαμβανόμενα, όσο συνεχίζει να ισχύει μια συνθήκη. Την πρώτη φορά που θα πάψει να ισχύει η συνθήκη, η λειτουργία σταματά να εκτελείται. Προφανώς για να σταματήσει κάποια στιγμή η λειτουργία θα πρέπει μέσα σε κάποια επανάληψη να συμβεί κάτι που κάνει τη συνθήκη να σταματήσει να ικανοποιείται.

Για παράδειγμα, έστω ότι έχουμε ένα διάνυσμα με ηλικίες ατόμων, και θέλουμε να βρούμε τη μέση τιμή των ηλικιών των πρώτων ατόμων που έχουν ηλικία έως το πολύ 20, δηλαδή από το άτομο 1 μέχρι το πρώτο που θα συναντήσουμε που έχει ηλικία πάνω από 20 (αυτό δεν θα υπολογιστεί στη μέση τιμή).

v=c(10, 8, 12, 20, 15, 22, 23, 12, 23, 32)

s=0 # Μεταβλητή που αθροίζει διαδοχικα τις ηλικίες
i=1 # Ξεκινάμε από το άτομο 1
while (v[i] <= 20)
{
  s=s+v[i]
  i=i+1
}
   # όταν σταματήσει το loop η i θα έχει τιμή μεγαλύτερη κατά 1 από τον αριθμό των ατόμων που μετρήθηκαν. 
  # Επομένως η μέση τιμή που θέλουμε είναι s/(i-1)
  # Αν βέβαια το πρώτο άτομο είναι ήδη μεγαλύτερο του 20, το i στο τέλος θα είναι 1 και μέση τιμή δεν υπάρχει. 

if (i==1) 
{
  av=-2
  } else 
{
  av=s/(i-1)
}

av
## [1] 13

Στο παραπάνω παράδειγμα υπάρχει το πρόβλημα ότι αν το διάνυσμα έχει όλα τα στοιχεία <=20, τότε μετά το τελευταίο στοιχείο το i θα πάρει τιμή μεγαλύτερη απο το μέγεθος του διανύσματος και θα επιστραφεί σφάλμα. Για να το αποφύγουμε αυτό θα πρέπει στη συνθήκη του while να προσθέσουμε την i<=length(v)

v=c(10, 8, 12, 20, 15, 12, 13, 12, 13, 12)

n=length(v)
s=0 
i=1 
while (v[i] <= 20 & i<=n )
{
  s=s+v[i]
  i=i+1
}
  
if (i==1) 
{
  av=-2
  } else 
{
  av=s/(i-1)
}

av
## [1] 12.7

6.4 Αποφυγή for loops με συναρτήσεις της κατηγορίας apply

Οι αλγοριθμικές διαδικασίες for, if..else, while είναι γενικά πολύ χρήσιμες για τον προγραμματισμό περίπλοκων λειτουργιών σε δομές δεδομένων του R. Όμως γενικά είναι απαιτητικές σε χρόνο και μνήμη και ιδιαίτερα σε πολύ μεγάλα σύνολα δεδομένων μπορεί να προκαλέσουν σημαντικές καθυστερήσεις στην εκτέλεση των προγραμμάτων. Επειδή το R είναι μια γλώσσα που βασίζεται σε πολύ μεγάλο βαθμό σε δομές όπως οι πίνακες και τα διανύσματα, έχουν δημιουργηθεί συναρτήσεις που εκτελούν επαναληπτικά λειτουργίες στις γραμμές και τις στήλες ενός πίνακα με πολύ πιο γρήγορο τρόπο από ότι οι γενικές διαδικασίες που είδαμε παραπάνω. Για αυτό το λόγο είναι σημαντικό όποιος ασχολείται με δημιουργία και διαχείριση μεγάλων συνόλων δεδομένων να τις γνωρίζει και να μπορεί να τις εφαρμόζει όσο το δυνατό περισσότερο, έτσι ώστε τα προγράμματα να τρέχουν πιο γρήγορα και αποδοτικά. Η γενική κατηγορία αυτών των συναρτήσεων συναρτήσεων είναι οι συναρτήσεις τύπου apply.

6.4.1 Η συνάρτηση apply

Ας θεωρήσουμε ένα πίνακα x διαστάσεων 4Χ6:

x=matrix(1:24,nrow=4)
x
##      [,1] [,2] [,3] [,4] [,5] [,6]
## [1,]    1    5    9   13   17   21
## [2,]    2    6   10   14   18   22
## [3,]    3    7   11   15   19   23
## [4,]    4    8   12   16   20   24

Έστω ότι θέλουμε να δημιουργήσουμε ένα διάνυσμα s που περιέχει τα αθροίσματα των γραμμών του πίνακα x. Αυτό μπορεί να γίνει δημιουργώντας ένα for loop, που υπολογίζει το άθροισμα των στοιχείων κάθε γραμμής χρησιμοποιώντας την εντολή sum, και τοποθετεί το άθροισμα στο αντίστοιχο στοιχείο ενός διανύσματος s διάστασης 4:

s=rep(0,4)
for (i in 1:4)
{
    s[i]=sum(x[i,])
}
s
## [1] 66 72 78 84

Η παραπάνω διαδικασία θα μπορούσε να προγραμματιστεί πιο σύντομα χρησιμοποιώντας τη συνάρτηση apply. Η συνάρτηση έχει τη γενική σύνταξη

apply(x, Margin, Function)

όπου: x είναι ένα διάνυσμα (διάσταση 1) ή ένας πίνακας (διάσταση 2), και Function είναι μια συνάρτηση που θέλουμε να εφαρμοστεί επαναληπτικά στα στοιχεία του πίνακα. Η παράμετρος Margin καθορίζει τον τρόπο επαναληπτικής εφαρμογής. Συγκεκριμένα αν Margin=1 η εφαρμογή γίνεται σε όλες τις γραμμές μια προς μια, αν Margin=2 η εφαρμογή γίνεται σε όλες τις στήλες μια προς μια, ενώ αν Margin=c(1,2) η συνάρτηση εφαρμόζεται σε όλα τα στοιχεία του πίνακα ένα προς ένα.

Στο παραπάνω παράδειγμα, θέλουμε να εφαρμόσουμε τη συνάρτηση sum σε όλες τις γραμμές του πίνακα x μια προς μια. Επομένως η κατάλληλη εντολή είναι

s=apply(x,1,sum)
s
## [1] 66 72 78 84

Χρησιμοποιώντας Margin=2, μπορούμε αντίστοιχα να υπολογίσουμε τα αθροίσματα των στηλών:

t=apply(x,2,sum)
t
## [1] 10 26 42 58 74 90

Στη θέση της συνάρτησης sum θα μπορούσαμε να χρησιμοποιήσουμε οποιαδήποτε άλλη συνάρτηση μπορεί να εφαρμοστεί πάνω σε διανύσματα, π.χ. mean, sd κλπ. Επίσης μπορούμε να χρησιμοποιήσουμε συναρτήσεις που έχουμε ορίσει εμείς και όχι μόνο προκαθορισμένες συναρτήσεις του R. Για παράδειγμα μπορούμε να εφαρμόσουμε τη συνάρτηση cube που ορίσαμε παραπάνω και υπολογίζει την τρίτη δύναμη ενός αριθμού, για να υπολογίσουμε τους κύβους όλων των στοιχείων του x ένα προς ένα ως εξής:

y=apply(x,c(1,2),cube)
y
##      [,1] [,2] [,3] [,4] [,5]  [,6]
## [1,]    1  125  729 2197 4913  9261
## [2,]    8  216 1000 2744 5832 10648
## [3,]   27  343 1331 3375 6859 12167
## [4,]   64  512 1728 4096 8000 13824

Αν η συνάρτηση που θέλουμε να εφαρμόσουμε μέσω της εντολής apply έχει επιπλέον ορίσματα, οι τιμές τους μέσα στην εντολή apply δίνονται μετά το όνομα της συνάρτησης, χρησιμοποιώντας τη σύνταξη arg=val, όπου arg το όνομα του ορίσματος όπως αυτό δίνεται στον ορισμό της συνάρτησης και val η τιμή που θέλουμε να πάρει. Για παράδειγμα ας ορίσουμε μια συνάρτηση pow που υπολογίζει τη n-οστή δύναμη ενός αριθμού x:

pow=function(a,n)
{
  return(a^n)
}

Έστω ότι θέλουμε να εφαρμόσουμε τη συνάρτηση pow στα στοιχεία του πίνακα x, για να υπολογίσουμε την τετραγωνική ρίζα καθενός από αυτά. Μπορούμε να εφαρμόσουμε μέσω της εντολής apply τη συνάρτηση pow δίνοντας στο όρισμα n την τιμή 2:

s=apply(x,c(1,2),pow,n=1/2)
s
##          [,1]     [,2]     [,3]     [,4]     [,5]     [,6]
## [1,] 1.000000 2.236068 3.000000 3.605551 4.123106 4.582576
## [2,] 1.414214 2.449490 3.162278 3.741657 4.242641 4.690416
## [3,] 1.732051 2.645751 3.316625 3.872983 4.358899 4.795832
## [4,] 2.000000 2.828427 3.464102 4.000000 4.472136 4.898979

Στη θέση του ορίσματος που παραλείπεται μέσα στην εντολή apply χρησιμοποιείται το αντίστοιχο τμήμα του πίνακα x. Για παράδειγμα στην προηγούμενη εντολή δίνεται τιμή μόνο στο όρισμα n της συνάρτησης pow. Στη θέση του ορίσματος a χρησιμοποιείται κάθε φορά το στοιχείο του πίνακα x. Με αντίστοιχο τρόπο, αν δώσουμε τιμή στο όρισμα a και παραλείψουμε το όρισμα n της συνάρτησης pow μέσα στην apply, μπορούμε να υπολογίσουμε τις δυνάμεις του a, με εκθέτες τα στοιχεία του πίνακα x:

w=apply(x,c(1,2),pow,a=0.9)
w
##        [,1]      [,2]      [,3]      [,4]      [,5]       [,6]
## [1,] 0.9000 0.5904900 0.3874205 0.2541866 0.1667718 0.10941899
## [2,] 0.8100 0.5314410 0.3486784 0.2287679 0.1500946 0.09847709
## [3,] 0.7290 0.4782969 0.3138106 0.2058911 0.1350852 0.08862938
## [4,] 0.6561 0.4304672 0.2824295 0.1853020 0.1215767 0.07976644

Στην κατηγορία αυτών των συναρτήσεων εκτός από την apply υπάρχουν και πολλές άλλες (lapply, sapply, mapply, κλπ) που έχουν πιο γενικές ή πιο εξειδικευμένες λειτουργίες.

7 Στατιστικές Λειτουργίες

7.1 Κατανομές Πιθανότητας

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

Τα δυνατά προθέματα είναι: d για συνάρτηση πυκνότητας πιθανότητας για συνεχείς ή συνάρτηση μάζας πιθανότητας για διακριτές κατανομές, p για αθροιστική συνάρτηση κατανομής, q για ποσοστημόρια και r για τυχαίους αριθμούς από τη συγκεκριμένη κατανομή.

Μερικές προκαθορισμένες κατανομές είναι: Γεωμετρική (geom), Διωνυμική(binom), Poisson(pois), Υπεργεωμετρική (hyper), Ομοιόμορφη (unif), Εκθετική (exp), Κανονική (norm), Λογαριθμοκανονική (lnorm), Βήτα (beta), Γάμμα (gamma), t-student (t) κλπ.

Ανάλογα με τις παραμέτρους της κατανομής και το πρόθεμα που χρησιμοποιείται η συνάρτηση που καλείται παίρνει διαφορετικά ορίσματα. Παραδείγματα:

Έστω X διωνυμική τυχαία μεταβλητή με \(n=10, p=0.4\) και ζητούνται οι πιθανότητες \(P(X=5), P(X \leq 4)\):

dbinom(5,10,0.4)
## [1] 0.2006581
pbinom(4, 10, 0.4)
## [1] 0.6331033

Έστω X κανονική τυχαία μεταβλητή με \(\mu=10, \sigma^2=16 (\sigma=4)\) και ζητούνται το 55% ποσοστημόριο της κατανομής και 5 τυχαίες παρατηρήσεις από αυτήν:

qnorm(0.55, 10, 4)
## [1] 10.50265
rnorm(5, 10, 4)
## [1]  8.322931  7.267497  6.942045 10.105432 12.832802

7.2 Βασικές Στατιστικές Εντολές

Οι βασικές εντολές για στατιστική ανάλυση αναφέρονται στον υπολογισμό ποσοτικών μεγεθών περιγραφικής στατιστικής. Για ένα διάνυσμα που περιέχει ένα τυχαίο δείγμα από μια κατανομή μπορούμε να υπολογίσουμε Μέση τιμή (mean), Διασπορά (var), Τυπική Απόκλιση (sd), Διάμεσο (median), κλπ. Περίληψη των ποσοστημορίων του δείγματος μπορούμε να πάρουμε με την εντολή summary:

mean(ozone$ozone)
## [1] 42.0991
sd(ozone$ozone)
## [1] 33.27597
summary(ozone$ozone)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##     1.0    18.0    31.0    42.1    62.0   168.0

Αν υποθέσουμε ότι το δείγμα προέρχεται από κανονική κατανομή με άγνωστη μέση τιμή και διασπορά, με την εντολή t.test μπορούμε να κάνουμε έλεγχο \(t\) για τη μέση τιμή: \(H_0: \mu=\mu_0, H_1: \mu (\neq,>,<) \mu_0\), όπως επίσης και να υπολογίσουμε διαστήματα εμπιστοσύνης. Το επίπεδο σημαντικότητας \(\alpha\) ορίζεται μέσω της παραμέτρου conf.level=\(1-\alpha\). Η μορφή της εναλλακτικής δίνεται από την παράμετρο alternative, που μπορεί να πάρει τιμές “two.sided”, “less”, “greater”. Η υποτιθέμενη τιμή \(\mu_0\) δίνεται από την παράμετρο \(mu\).

t.test(ozone$ozone, mu=35, alternative = "two.sided")
## 
##  One Sample t-test
## 
## data:  ozone$ozone
## t = 2.2477, df = 110, p-value = 0.02659
## alternative hypothesis: true mean is not equal to 35
## 95 percent confidence interval:
##  35.83986 48.35834
## sample estimates:
## mean of x 
##   42.0991
t.test(ozone$ozone, mu=35, alternative = "greater")
## 
##  One Sample t-test
## 
## data:  ozone$ozone
## t = 2.2477, df = 110, p-value = 0.0133
## alternative hypothesis: true mean is greater than 35
## 95 percent confidence interval:
##  36.85984      Inf
## sample estimates:
## mean of x 
##   42.0991
t.test(ozone$ozone, mu=35, alternative = "less")
## 
##  One Sample t-test
## 
## data:  ozone$ozone
## t = 2.2477, df = 110, p-value = 0.9867
## alternative hypothesis: true mean is less than 35
## 95 percent confidence interval:
##      -Inf 47.33835
## sample estimates:
## mean of x 
##   42.0991

Η εντολή t.test χρησιμοποιείται επίσης και για έλεγχο μέσων τιμών δύο διαφορετικών πληθυσμών. Σε αυτή την περίπτωση καλείται με τα δύο πρώτα ορίσματα να είναι τα δείγματα από τους δύο πληθυσμούς με άγνωστες μέσες τιμές \(\mu_1, \mu_2\) και ο έλεγχος που γίνεται είναι \(H_0: \mu_1=\mu_2, H_1: \mu_1 (\neq,>,<) \mu_2\). Η παράμετρος var.equal(=T,F, default=F) δηλώνει αν οι διασπορές των δύο πληθυσμών θεωρούνται ίσες ή διαφορετικές, ενώ η παράμετρος paired(=T,F, default=F) χρησιμοποιείται αν ο έλεγχος είναι paired t-test.

Για παράδειγμα έστω ότι στο dataset ozone θέλουμε να ελέγξουμε αν οι μέσες τιμές όζοντος διαφέρουν ανάμεσα στις μέρες με θερμοκρασία κάτω από τη μέση θερμοκρασία των ημερών του δείγματος και στις μέρες με θερμοκρασία πάνω από τη μέση τιμή. Βρίσκουμε τη μέση τιμή της θερμοκρασίας και δημιουργούμε δύο διανύσματα με τις τιμές του όζοντος στα δύο παραπάνω υποσύνολα:

mt=mean(ozone$temperature)
ozone1=ozone[ozone$temperature<=mt,]$ozone
ozone2=ozone[ozone$temperature>mt,]$ozone
t.test(ozone1, ozone2)
## 
##  Welch Two Sample t-test
## 
## data:  ozone1 and ozone2
## t = -9.4959, df = 70.619, p-value = 3.016e-14
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  -51.60827 -33.69469
## sample estimates:
## mean of x mean of y 
##  18.66000  61.31148

Παρατηρούμε ότι υπάρχει στατιστικά σημαντική ένδειξη ότι οι μέσες τιμές των δύο πληθυσμών διαφέρουν.

7.3 Ανάκτηση Μεταβλητών από Αποτελέσματα Ανάλυσης

Ένα από τα πιο σημαντικά πλεονεκτήματα του R που επιτρέπουν το συνδυασμό στατιστικής ανάλυσης και προγραμματισμού είναι η δυνατότητα εύκολης ανάκτησης αποθήκευσης μεμονωμένων αποτελεσμάτων μιας στατιστικής ανάλυσης για χρήση τους σε προγράμματα που δημιουργείο ο χρήστης. Γενικά οποιαδήποτε εντολή στατιστικής ανάλυσης είναι στην πραγματικότητα μια συνάρτηση που καλείται με συγκεκριμένα ορίσματα και το αποτέλεσμά της μπορεί να αποθηκευτεί σε μια μεταβλητή. Για παράδειγμα ένα t.test όπως τα παραπάνω μπορεί να εκτελεστεί με τον παρακάτω τρόπο:

a=t.test(ozone$ozone, mu=35, alternative = "two.sided")

Τώρα η μεταβλητή a είναι ένα αντικείμενο που περιέχει όλα τα αποτελέσματα του t-test που εμφανίζονται στην οθόνη όταν η συνάρτηση t.test κληθεί χωρίς ανάθεση:

a
## 
##  One Sample t-test
## 
## data:  ozone$ozone
## t = 2.2477, df = 110, p-value = 0.02659
## alternative hypothesis: true mean is not equal to 35
## 95 percent confidence interval:
##  35.83986 48.35834
## sample estimates:
## mean of x 
##   42.0991

Όμως το κυριότερο πλεονέκτημα είναι ότι η μεταβλητή a είναι ένα αντικείμενο τύπου list που περιέχει όλα τα αποτελέσματα του t-test το καθένα με το όνομά του. Τα ονόματα των επιμέρους στοιχείων του a μπορούμε να τα δούμε με τη συνάρτηση names:

is.list(a)
## [1] TRUE
names(a)
##  [1] "statistic"   "parameter"   "p.value"     "conf.int"    "estimate"   
##  [6] "null.value"  "stderr"      "alternative" "method"      "data.name"

Επίσης μπορούμε να ανακτήσουμε οποιοδήποτε στοιχείο του a μεμονωμένα:

p=a$p.value
p
## [1] 0.02659124
cint=a$conf.int
cint
## [1] 35.83986 48.35834
## attr(,"conf.level")
## [1] 0.95

Μπορούμε να εκμεταλλευτούμε τη δυνατότητα αυτή για να ενσωματώνουμε τα αποτελέσματα των αναλύσεων σε μεγαλύτερα προγράμματα. Για παράδειγμα έστω ότι θέλουμε να δούμε πώς μεταβάλλεται το διάστημα εμπιστοσύνης για τη μέση τιμή του ozone, αν μεταβάλλουμε το επίπεδο εμπιστοσύνης μεταξύ 80 και 99%. Δημιουργούμε ένα διάνυσμα τιμών για το επίπεδο εμπιστοσύνης \(c=(0.8, 0.81, \ldots, 0.99)\). Δημιουργούμε ένα πίνακα results με τόσες γραμμές όσα και τα στοιχεία του c και 2 στήλες. Σε κάθε γραμμή του πίνακα θα αποθηκευτούν τα άκρα του διαστήματος εμπιστοσύνης για την αντίστοιχη τιμή του c. Τέλος δημιουργούμε ένα for loop που θα επαναλάβει το t.test και θα αποθηκεύσει το conf.int για κάθε τιμή του c:

c=seq(0.81, 0.99, by=0.01)
nc=length(c)
results=matrix(rep(0, 2*nc), nc, 2)
for (i in 1:nc)
{
  w=t.test(ozone$ozone, mu=0, conf.level = c[i])
  results[i,]=w$conf.int
}
results  
##           [,1]     [,2]
##  [1,] 37.93402 46.26418
##  [2,] 37.83734 46.36086
##  [3,] 37.73652 46.46168
##  [4,] 37.63109 46.56710
##  [5,] 37.52049 46.67771
##  [6,] 37.40402 46.79417
##  [7,] 37.28088 46.91732
##  [8,] 37.15004 47.04816
##  [9,] 37.01023 47.18797
## [10,] 36.85984 47.33835
## [11,] 36.69676 47.50144
## [12,] 36.51815 47.68005
## [13,] 36.32008 47.87811
## [14,] 36.09688 48.10131
## [15,] 35.83986 48.35834
## [16,] 35.53469 48.66351
## [17,] 35.15499 49.04321
## [18,] 34.64295 49.55525
## [19,] 33.82006 50.37814

Τελειώνοντας αυτή την ενότητα, πρέπει να κάνουμε μια πολύ σημαντική παρατήρηση. Το γεγονός ότι τα αποτελέσματα των στατιστικών συναρτήσεων όπως η t.test είναι lists με συγκεκριμένα ονόματα στοιχείων δεν είναι αποκλειστικό χαρακτηριστικό αυτών των συναρτήσεων, αλλά μια γενική δυνατότητα που δίνει η γλώσσα προγραμματισμού R. Σε οποιαδήποτε συνάρτηση που δημιουργεί ο χρήστης, μπορεί το αποτέλεσμα να δοθεί σε μορφή λίστας με συγκεκριμένα ονόματα, έτσι ώστε τα επιμέρους στοιχεία της λίστας να μπορούν να ανακτηθούν εύκολα μετά την κλήση της συνάρτησης. Για παράδειγμα ας δούμε τη συνάρτηση circletest που δημιουργήσαμε σε προηγούμενη ενότητα για τον υπολογισμό της απόστασης ενός σημείου από την αρχή των αξόνων και τον έλεγχο αν βρίσκεται μέσα στον κύκλο :

circletest
## function(x,y,r)
## {
##   d=sqrt(x^2+y^2)
##   incircle=(d <= r)
##   result=list(d,incircle)
##   return(result)
## }

Αν καλέσουμε αυτή τη συνάρτηση, το αποτέλεσμα είναι μια λίστα με δύο στοιχεία που μπορούμε να τα ανακτήσουμε όπως τα στοιχεία μιας λίστας:

a=circletest(2,3,2)
a
## [[1]]
## [1] 3.605551
## 
## [[2]]
## [1] FALSE
a[[1]]
## [1] 3.605551
a[[2]]
## [1] FALSE

Εναλλακτικά, θα μπορούσαμε μέσα στον ορισμό της συνάρτησης, να δώσουμε ονόματα στα στοιχεία της λίστας results, έτσι ώστε αυτά να είναι ανακτήσιμα κατά την κλήση της συνάρτησης:

circletest=function(x,y,r)
{
  d=sqrt(x^2+y^2)
  incircle=(d <= r)
  result=list(d,incircle)
  names(result)=c("distance", "inside")
  return(result)
}

Τώρα το αποτέλεσμα της συνάρτησης είναι μια λίστα με στοιχεία που έχουν τα συγκεκριμένα ονόματα:

a=circletest(2,3,2)
a
## $distance
## [1] 3.605551
## 
## $inside
## [1] FALSE
a$distance
## [1] 3.605551
a$inside
## [1] FALSE

7.4 Αντικείμενα τύπου formula για ορισμό στατιστικών μοντέλων

Για την εκτίμηση παραμέτρων σε προβλήματα παλινδρόμησης, ανάλυσης διασποράς, glm, ανάλυσης επιβίωσης κλπ συνήθως υπάρχει μια εξαρτημένη μεταβλητή (απόκριση, αποτέλεσμα) και μια ή περισσότερες ανεξάρτητες μεταβλητές (παράγοντες) και σκοπός της ανάλυσης είναι η εκτίμηση των συσχετίσεων μεταξύ της απόκρισης και των παραγόντων. Για το σκοπό αυτό στις στατιστικές μελέτες συλλέγονται δείγματα που περιέχουν παρατηρήσεις (ατόμων, ασθενών, κλπ) κάθε μια από τις οποίες έχει μετρήσεις της εξαρτημένης και των ανεξάρτητων μεταβλητών. Στο R το τυπικό σενάριο είναι ένα τέτοιο δείγμα να αποθηκεύεται ως data frame με γραμμές τις παρατηρήσεις και στήλες τις μεταβλητές. Τα ονόματα των μεταβλητών είναι και τα ονόματα των στηλών.

Σε ένα δείγμα όπως το παραπάνω, έστω \(Y\) η εξαρτημένη μεταβλητή και \(X_1, \ldots, X_p\) οι ανεξάρτητες. Για την ανάλυση ενός στατιστικού μοντέλου με βάση αυτό, πρέπει να γίνει μια υπόθεση για τη σχέση που συνδέει την εξαρτημένη με τις ανεξάρτητες μεταβλητές. Η σχέση αυτή περιέχει και κάποιες άγνωστες παραμέτρους και (ένας) σκοπός της ανάλυσης είναι να εκτιμήσει τις τιμές τους. Για παράδειγμα σε ένα γραμμικό μοντέλο υποθέτουμε ότι ισχύει η σχέση \(Y = \beta_0 + \beta_1 X_1 + \cdots + \beta_p X_p + \epsilon\), όπου \(\epsilon\) είναι μια τυχαία διαταραχή με κανονική κατανομή. Σε άλλα μοντέλα γίνονται διαφορετικές υποθέσεις αλλά συνήθως υπάρχει μια υποκείμενη συναρτησιακή σχέση ανάμεσα στις μεταβλητές.

Στο \(R\) μια συναρτησιακή σχέση μεταξύ μεταβλητών που ορίζει ένα στατιστικό μοντέλο αποθηκεύεται σε αντικείμενο τύπου formula. Η ύπαρξη αυτής της κλάσης δίνει μεγάλη ευελιξία στο σχεδιασμό στατιστικών αναλύσεων καθώς επιτρέπει να ορίζονται μοντέλα μέσω επαναληπτικών αλγορίθμων όπως επίσης και να χρησιμοποιούνται τα ίδια στατιστικά μοντέλα σε διαφορετικά δείγματα που έχουν αποθηκευτεί σε διαφορετικά data frames, αρκεί αυτά να έχουν τα ίδια ονόματα μεταβλητών.

Ένα αντικείμενο τύπου formula έχει τη γενική σύνταξη : \(y \sim x1+x2+\ldots\), όπου το σύμβολο ~ δηλώνει ότι το y είναι συνάρτηση των μεταβλητών x1, x2, κλπ, και το πώς ακριβώς ερμηνεύεται εξαρτάται από τη στατιστική εντολή η οποία καλεί αυτό το αντικείμενο. Για παράδειγμα αν θεωρήσουμε το dataframe ozone που είδαμε παραπάνω, μπορούμε να ορίσουμε τη formula

mod1=ozone$ozone~ozone$temperature

Αυτό το αντικείμενο μπορούμε να το χρησιμοποιήσουμε με την εντολή plot για να δημιουργήσουμε ένα scatterplot

plot(mod1)

είτε με την εντολή lm για να τρέξουμε ένα μοντέλο απλής γραμμικής παλινδρόμησης

lm(mod1)
## 
## Call:
## lm(formula = mod1)
## 
## Coefficients:
##       (Intercept)  ozone$temperature  
##          -147.646              2.439

Μια σημαντική ευελιξία που δίνουν τα αντικείμενα formula είναι το γεγονός ότι δεν συνδέονται απαραίτητα με ένα συγκεκριμένο dataframe. Για παράδειγμα αν ορίσουμε τη formula

mod2=ozone~temperature

και δώσουμε πάλι την εντολή plot(mod2) παίρνουμε σφάλμα, γιατί οι μεταβλητές ozone, temperature δεν έχουν οριστεί ως αυτόνομα αντικείμενα αλλά ως στήλες του dataframe ozone. Αυτός ο ορισμός όμως μας δίνει την ευελιξία να τρέξουμε το ίδιο μοντέλο σε διαφορετικά dataframes, αρκεί αυτά να έχουν τα ίδια ονόματα στηλών. Για να γίνει αυτό πρέπει στην αντίστοιχη εντολή να δοθεί και η παράμετρος data που δηλώνει από ποιό dataframe θα ληφθούν τα δεδομένα:

plot(mod2, data=ozone)

Αυτός ο τρόπος συνδυασμού dataframes και formulas είναι ο πιο σωστός προγραμματιστικά, καθώς αποδεσμεύει τα μοντέλα που χρησιμοποιούμε στις αναλύσεις μας από συγκεκριμένα datasets. Για παράδειγμα είναι πολύ χρήσιμος όταν θέλουμε να δημιορυγήσουμε plots ή να κάνουμε ανάλυση σε υποσύνολα του αρχικού δείγματος (π.χ. μόνο για άντρες, ή μόνο για ηλικίες άνω των 50 κλπ).

8 Plotting

8.1 Βασικές Εντολές Γραφημάτων

Οι πιο συνηθισμένες συναρτήσεις γραφημάτων είναι οι hist για ιστογράμματα, η boxplot για θηκογράμματα και η plot για scatterplots. Στα παραδείγματα αυτής της ενότητας θα χρησιμοποιήσουμε το dataframe bone, που περιέχει δεδομένα σχετικά με ηλικία, φύλο και μια τυποποιημένη μέτρηση οστικής πυκνότητας για 485 άτομα μιας μελέτης.

load("bone.Rdata")
names(bone)
## [1] "idnum"  "age"    "gender" "spnbmd"

8.2 Οι συναρτήσεις hist και boxplot

Η συνάρτηση hist δημιουργεί ιστόγραμμα από ένα διάνυσμα τιμών.

hist(bone$spnbmd)

Με την παράμετρο col μπορούμε να επιλέξουμε το χρώμα, δίνοντας είτε τον αριθμό του χρώματος είτε το όνομα.

hist(bone$spnbmd, col="red")

Με την παράμετρο \(nclass\) ορίζεται ο αριθμός των κλάσεων (δεν ακολουθείται ακριβώς) και με την παράμετρο breaks ορίζονται συγκεκριμένα διαστήματα δίνοντας το διάνυσμα των διαδοχικών άκρων των διαστημάτων. Τα διαστήματα δεν είναι απαραίτητο να έχουν το ίδιο μήκος, όμως πρέπει να εχουν αρκετό εύρος ώστε να περιλαμβάνονται σε αυτά όλες οι τιμές του διανύσματος.

hist(bone$spnbmd, nclass=20)

summary(bone$spnbmd)
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
## -0.064103  0.005858  0.026591  0.039252  0.064127  0.219913
hist(bone$spnbmd, breaks=c(-0.1, 0, 0.05, 0.15, 0.25))

Η συνάρτηση boxplot χρησιμοποείται για να δημιουργήσει θηκόγραμμα ενός διανύσματος τιμών.

boxplot(bone$spnbmd)

Αν δοθούν περισσότερα από ένα διανύσματα, δημιουργούνται πολλαπλά θηκογράμματα το ένα δίπλα στο άλλο. Μπορούν να έχουν διαφορετικά χρώματα αν η παράμετρος col οριστεί ως διάνυσμα χρωμάτων. Με την παράμετρο names μπορούμε να ορίσουμε τα ονόματα των αντίστοιχων διαγραμμάτων. Για παραάδειγμα δημιουργούμε δύο χωριστά θηκογράμματα των ηλικιών ανά φύλο.

agem=bone[bone$gender=="male",]$age
agef=bone[bone$gender=="female",]$age
boxplot(agem, agef, col=c("blue", "red"), names=c("M", "F"))

8.3 H συνάρτηση plot

Η συνάρτηση plot είναι από τις πιο χρήσιμες συναρτήσεις γραφημάτων και δημιουργεί scatterplots. Οι μεταβλητές του γραφήματος ορίζονται μέσω αντικειμένου formula, π.χ. y~x, όπου x, y οι μεταβλητές στον οριζόντιο και στον κάθετο άξονα, αντίστοιχα.

plot(bone$spnbmd~bone$age)

Η παράμετρος cex ορίζει το μέγεθος των συμβόλων (σχετικά ως προς το 1) και η παράμετρος pch το σύμβολο των σημείων του διαγράμματος. Η col ορίζει το χρώμα. Επίσης μπορούν να προστεθούν τίτλοι στο διάγραμμα και στους άξονες μέσω των παραμέτρων main, xlab, ylab

plot(bone$spnbmd~bone$age, cex=0.6, pch="x", col="blue", main="Bone Density Diagram", xlab="Age", ylab="Density")

Με τις παραμέτρους xlim, ylim μπορούν να οριστούν όρια των αξόνων. Αν παραλειφθούν οι παράμετροι, τα όρια ορίζονται αυτόματα με βάση τις τιμές των σημείων του διαγράμματος. Αν οριστούν οι xlib, ylib, σημεία του διαγράμματος που βρίσκονται εκτός των ορίων παραλείπονται.

x=1:10
y=x^2
plot(y~x)

plot(y~x, ylim=c(-10, 40))

Με την παράμετρο type ορίζεται ο τύπος του διαγράμματος (p= μεμονωμένα σημεία, l=σημεία χωρίς σύμβολα ενωμένα ευθύγραμμα τμήματα, b=σημεία με σύμβολα ενωμένα με ευθύγραμμα τμήματα, κλπ). Για διαγράμματα τύπου l, b, θα πρέπει οι τιμές των δεδομένων που αντιστοιχούν στον οριζόντιο άξονα να είναι σε αύξουσα σειρά, διαφορετικά το διάγραμμα δεν έχει νόημα. Ένα dataframe μπορεί να διαταχθεί ως προς τις τιμές μιας ή περισσότερων στηλών μέσω της συνάρτησης order

ordbone=bone[order(bone$age),]
plot(ordbone$spnbmd~ordbone$age)

plot(ordbone$spnbmd~ordbone$age, type="l")