Θα χρησιμοποιήσουμε το πρόγραμμα RStudio, το οποίο διατίθεται δωρεάν στο διαδίκτυο (http://www.rstudio.com)

1. Υπολογισμοί με την R

Στο RStudio, οι εντολές μπορούν να πληκτρολογούνται στο παράθυρο Console (κονσόλα). Οι εντολές μαρκάρονται με το σύμβολο >, ενώ τα αποτελέσματα αριθμούνται και μαρκάρονται με [1], [2] κλπ. (τα σύμβολα αυτά εμφανίζονται αυτόματα).

Για παράδειγμα, μπορούμε να υπολογίσουμε το άθροισμα δύο αριθμών

3+4
## [1] 7

ή να κάνουμε γραφικές παραστάσεις:

plot(c(1,2,3,4), c(1,4,5,2), xlab="x", ylab="y", type="o")

1.1. Μαθηματικές πράξεις

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

Πράξη/Συνάρτηση Παράδειγμα Κώδικας R
Πρόσθεση 5+2 5+2
Αφαίρεση 5-2 5-2
Πολλαπλασιασμός 5.2 5*2
Διαίρεση 5/2 5/2
Δύναμη \(5^2\) 5^2
Modulus 5mod2 5%%2
Απόλυτη τιμή \(\lvert x\rvert\) abs(x)
Τετραγωνική ρίζα \(\sqrt{x}\) sqrt(x)
Εκθετικό \(e^x\) exp(x)
Φυσικός λογάριθμος log(x) log(x)
Ημίτονο \(\sin(2\pi x)\) sin(2 * pi * x)
Συνημίτονο \(\cos(2\pi x)\) cos(2 * pi * x)

1.2. Μεταβλητές

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

a<-1
b<-4
c<-2
root1<-(-b+sqrt(b^2-4*a*c))/(2*a)
root2<-(-b-sqrt(b^2-4*a*c))/(2*a)
root1
## [1] -0.5857864
root2
## [1] -3.414214
root1*root2
## [1] 2

H ονομασία των μεταβλητών μπορεί να συνδιάζει λατινικά γράμματα, αριθμούς και τελείες (πρέπει όμως να αρχίζει από γράμμα). Η ανάθεση της τιμής σε μια μεταβλητή γίνεται με τον τελεστή ανάθεσης <- ή απλά με το =.

Η R πρώτα υπολογίζει την τιμή της έκφρασης δεξιά από τον τελεστή ανάθεσης και ακολούθως αναθέτει το αποτέλεσμα στη μεταβλητή αριστερά.

x<-6
x<-x+1
x
## [1] 7

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

1.3. Τύποι δεδομένων

Υπάρχουν διάφοροι τύποι δεδομένων στην R (αριθμοί, χαρακτήρες, διανύσματα κλπ).

Αριθμοί
Ο πιο συνηθισμένος τύπος. Η R χρησιμοποιεί επιστημονικό συμβολισμό για μεγάλους αριθμούς:

exp(50)
## [1] 5.184706e+21

Οι υπολογιστές έχουν πεπερασμένη ακρίβεια και συνεπώς (εσωτερικά κατά τους υπολογισμούς) στρογγυλοποιούν πολύ μικρούς αριθμούς στο 0, ενώ πολύ μεγάλοι αριθμοί αναπαριστούνται ως άπειρο.

exp(-1000)
## [1] 0
exp(1000)
## [1] Inf

Αυτό μπορεί να προκαλέσει προβλήματα σε πράξεις που συνδυάζουν πολύ μεγάλους και πολύ μικρούς κατ’απόλυτη τιμή αριθμούς.

(1e15+0.2)-1e15
## [1] 0.25
x=1e-1000/(1e-1000+1e-1001)
x
## [1] NaN

Σε τέτοιες περιπτώσεις ξαναγράφουμε τις εκφράσεις με τρόπο που να αποφεύγονται αυτού του τύπου υπολογισμοί.

Το NaN (not a number), δίνει το αποτέλεσμα υπολογισμών που δεν είναι μαθηματικά ορισμένοι.

0/0
## [1] NaN
log(-1)
## Warning in log(-1): NaNs produced
## [1] NaN

Διανύσματα
Χρήσιμα τόσο προγραμματιστικά όσο και για τη φύλαξη δεδομένων. Διάφοροι τρόποι δημιουργίας διανυσμάτων. Για παράδειγμα με τον τελεστή ένωσης c

c(1,2,3)
## [1] 1 2 3

Η πρόσβαση στα στοιχεία ενός διανύσματος γίνεται με τετραγωνικές αγκύλες.

x<-c(4,5,8,6)
x[2]
## [1] 5
x[1]+x[4]
## [1] 10
x[3]<-322
x
## [1]   4   5 322   6
x<-c(x,2)
x
## [1]   4   5 322   6   2
x<-c(x,4,5)
x
## [1]   4   5 322   6   2   4   5

Επίσης μπορούμε να ορίσουμε ένα κενό διάνυσμα και να προσθέσουμε 1-1 στοιχεία

x<-c()
x[1]<-2
x[2]<-1
x[3]<-6
x
## [1] 2 1 6
x[5]<-0
x
## [1]  2  1  6 NA  0

To ΝΑ σημαίνει ‘Not Available’ αφού δεν έχουμε δώσει τιμή για το 4ο στοιχείο του διανύσματος.

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

x<-c(1,2,3,4)
x
## [1] 1 2 3 4
x[-1]
## [1] 2 3 4
x[-c(2,3)]
## [1] 1 4

Διανύσματα με διαδοχικούς αυξανόμενους αριθμούς μπορούν να οριστούν με τον τελεστή “:”

1:15
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15

Πιο πολύπλοκα διανύσματα μπορούν να οριστούν με τη συνάρτηση seq:

seq(from=1, to=15, by=2)
## [1]  1  3  5  7  9 11 13 15
seq(from=1, to=1, by=-2)
## [1] 1

Μπορούμε να κάνουμε μαθηματικές πράξεις με διανύσματα

c(1,2,3)*3
## [1] 3 6 9
c(1,2,3)+c(2,4,6)
## [1] 3 6 9

Διάφορες χρήσιμες συναρτήσεις για διανύσματα είναι οι: sum (άθροισμα στοιχείων), mean (μέσος όρος στοιχείων), var (η δειγματική διακύμανση των στοιχείων), sd (δειγματική τυπική απόκλιση των στοιχείων), length (μήκος του διανύσματος).

x<-c(1,2,3)
(x[1]+x[2]+x[3])/3
## [1] 2
sum(x)/length(x)
## [1] 2
mean(x)
## [1] 2

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

x<-c(-1,0,1,2,3)
abs(x)
## [1] 1 0 1 2 3
x^2
## [1] 1 0 1 4 9

Άλλα χρήσιμα διανύσματα

rep(c(1,2,3), c(2,3,4))
## [1] 1 1 2 2 2 3 3 3 3
letters
##  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q"
## [18] "r" "s" "t" "u" "v" "w" "x" "y" "z"
letters[c(1,2,3)]
## [1] "a" "b" "c"

Πίνακες
Μπορούν να δημιουργηθούν με χρήση της συνάρτησης matrix

A<-matrix(c(1,2,3,4,5,6), nrow=2, ncol=3, byrow=TRUE)
A
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6

Η εντολή diag επιτρέπει τον ορισμό διαγώνιων πινάκων, αλλά και την εξαγωγή της διαγωνίου από έναν πίνακα.

diag(3)
##      [,1] [,2] [,3]
## [1,]    1    0    0
## [2,]    0    1    0
## [3,]    0    0    1
diag(c(1,2,3))
##      [,1] [,2] [,3]
## [1,]    1    0    0
## [2,]    0    2    0
## [3,]    0    0    3
matrix(0, nrow=3, ncol=2)
##      [,1] [,2]
## [1,]    0    0
## [2,]    0    0
## [3,]    0    0
diag(A)
## [1] 1 5

Η πρόσβαση στα στοιχεία ενός πίνακα γίνεται πάλι με τετραγωνικές αγκύλες. Αν A πίνακας, τότε A[i,j] δίνει το στοιχείο στην i γραμμή και j στήλη, A[i,] την i-γραμμή, Α[,j] την j-στήλη, ενώ μπορούμε να πάρουμε και υποπίνακες με χρήση του τελεστή “:”.

A<-matrix(c(1,2,3,4,5,6), nrow=3, ncol=2, byrow=TRUE)
A
##      [,1] [,2]
## [1,]    1    2
## [2,]    3    4
## [3,]    5    6
A[1,1]
## [1] 1
A[1,2]<-21
A
##      [,1] [,2]
## [1,]    1   21
## [2,]    3    4
## [3,]    5    6
A[1,]
## [1]  1 21
A[,1]
## [1] 1 3 5
A[1:2,]
##      [,1] [,2]
## [1,]    1   21
## [2,]    3    4
A[2:3,2]
## [1] 4 6

Μπορούμε επίσης να ορίσουμε κενό πίνακα και να γεμίσουμε τα στοιχεία του αργότερα.

A<-matrix(nrow=2,ncol=10)
A
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
## [1,]   NA   NA   NA   NA   NA   NA   NA   NA   NA    NA
## [2,]   NA   NA   NA   NA   NA   NA   NA   NA   NA    NA
A[1,]<-1
A[2,]<-1:10
A
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
## [1,]    1    1    1    1    1    1    1    1    1     1
## [2,]    1    2    3    4    5    6    7    8    9    10

Μπορούμε να κάνουμε πράξεις, όπως πρόσθεση και πολλαπλασιασμός, οι οποίες γίνονται στοιχείο προς στοιχείο. Για το γινόμενο πινάκων όπως το γνωρίζουμε από τη Γραμμική Άλγεβρα, χρησιμοποιούμε τον τελεστή “%*%“.

A<-matrix(c(1,2,2,3), nrow=2,ncol=2, byrow=TRUE)
A
##      [,1] [,2]
## [1,]    1    2
## [2,]    2    3
A%*%A
##      [,1] [,2]
## [1,]    5    8
## [2,]    8   13
x<-c(0,1)
A%*%x
##      [,1]
## [1,]    2
## [2,]    3
x%*%A%*%x
##      [,1]
## [1,]    3

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

Υπάρχουν πολλές δυνατότητες πράξεων με πίνακες: t(A) (ανάστροφος), solve(A) (ανίστροφος), rowSums(A) ή colSums(A) (άθροισματα σειρών και στηλών αντίστοιχα), rowMeans ή colMeans (μέσοι όροι σειρών και στηλών αντίστοιχα). Επίσης μπορούμε να λύσουμε γραμμικά συστήματα της μορφής \(Ax=b\) με χρήση της εντολής solve(A,b).

Kείμενο
Υπάρχουν δεδομένα που δεν είναι αριθμητικά αλλά αποτελούνται από μικρές φράσεις/χαρακτήρες (strings). Το κείμενο είναι χρήσιμο επίσης για την ονοματολογία αξόνων κλπ.

12+1
## [1] 13
"12"+"1"
## Error in "12" + "1": non-numeric argument to binary operator

Τα strings μπορούν να αποθηκευτούν ως μεταβλητές, ενώ μπορούν να ενωθούν με την εντολή paste

s<-paste("this","is","a","test")
paste(s, ": ", "a", "bra", "ca", "da", "bra", sep="")
## [1] "this is a test: abracadabra"
paste("x=", 12, sep="")
## [1] "x=12"

Το όρισμα sep δίνει με ποιο τρόπο θα γίνει ο διαχωρισμός μεταξύ των επιμέρους strings. Εξορισμού είναι ένα κενό διάστημα. Αν υπάρχουν ορίσματα στο paste που δεν είναι strings, τότε μετατρέπονται αυτόματα σε string.

Παράγοντες
Στη Στατιστική υπάρχουν μεταβλητές που είναι ποιοτικές και ονομάζονται παράγοντες. Για παράδειγμα το χρώμα μαλλιών, το φύλο, η καταγωγή κλπ.

x<-c("red", "black", "red", "red")
x
## [1] "red"   "black" "red"   "red"
x<-as.factor(x)
x
## [1] red   black red   red  
## Levels: black red

Στην πρώτη περίπτωση η R αντιλαμβάνεται τα στοιχεία απλά σαν 4 κείμενα, ενώ στη 2η αντιλαμβάνεται τα 4 στοιχεία ότι έχουν μια από τις 2 τιμές red ή black.

Λογικές Τιμές
H R χρησιμοποιεί τις ειδικές τιμές TRUE και FALSE (συντομογραφία T και F).

1<2
## [1] TRUE
3<2
## [1] FALSE
result<-1<2
result
## [1] TRUE

Όταν χρησιμοποιούνται σε πράξεις όπου αναμένονται αριθμητικές τιμές, οι λογικές τιμές μετατρέπονται σε T=1 και F=0. Για παράδειγμα αυτό είναι χρήσιμο όταν θέλουμε να μετρήσουμε πόσα στοιχεία μιας λίστας ικανοποιούν μια συνθήκη.

x<-runif(10)
x
##  [1] 0.37912890 0.88566155 0.49083229 0.91873486 0.90239742 0.62090640
##  [7] 0.33553051 0.01881849 0.96056250 0.16527847
x>1/2
##  [1] FALSE  TRUE FALSE  TRUE  TRUE  TRUE FALSE FALSE  TRUE FALSE
sum(x>1/2)
## [1] 5

Ο πιο κάτω πίνακας περιέχει διάφορους τελεστές σύγκρισης στην R:

Σύγκριση Παράδειγμα Κώδικας R
Αυστηρά μικρότερο \(x < y\) x < y
Μικρότερο ή ίσο \(x\leq y\) x <= y
Αυστηρά μεγαλύτερο \(x>y\) x > y
Μεγαλύτερο ή ίσο \(x\geq y\) x >= y
Ίσο \(x=y\) x == y
Άνισο \(x\neq y\) x != y

Πλαίσια δεδομένων
Τα πλαίσια δεδομένων (data frames) είναι ιδιαίτερα χρήσιμα στην ανάλυση πραγματικών δεδομένων. Επιτρέπουν το συνδυασμό διαφορετικών τύπων δεδομένων (αριθμούς, παράγοντες, κείμενο κλπ) σε ένα αντικείμενο. Μοιάζουν με πίνακα, του οποίου κάθε στήλη αντιστοιχεί σε μια μεταβλητή. Με την εντολή read.table(“filename”) μπορούμε να διαβάζουμε αρχεία δεδομένων τύπου txt. Με την εντολή read.csv(“filename”) μπορούμε να διαβάζουμε αρχεία δεδομένων τύπου csv. Οι στήλες στα πλαίσια δεδομένων έχουν ονόματα και αντιστοιχούν σε μεταβλητές. Μπορούμε να αποκτήσουμε πρόσβαση στις στήλες με τον τελεστή “$”.

data<-read.csv("worldcup.csv")
data<-data[1:10,]
data$Team
##  [1] Algeria     Japan       France      France      Cameroon   
##  [6] Uruguay     Ghana       Ghana       Netherlands Nigeria    
## 32 Levels: Algeria Argentina Australia Brazil Cameroon Chile ... USA
data$Team[2]
## [1] Japan
## 32 Levels: Algeria Argentina Australia Brazil Cameroon Chile ... USA
Team
## Error in eval(expr, envir, enclos): object 'Team' not found
attach(data)
Team
##  [1] Algeria     Japan       France      France      Cameroon   
##  [6] Uruguay     Ghana       Ghana       Netherlands Nigeria    
## 32 Levels: Algeria Argentina Australia Brazil Cameroon Chile ... USA
detach(data)

Μπορούμε να φτιάξουμε πλαίσια δεδομένων με διάφορους τρόπους. Για παράδειγμα μπορούμε να χρησιμοποιήσουμε την εντολή data.frame, ή να μετατρέψουμε ένα πίνακα σε πλαίσιο δεδομένων με την εντολη as.data.frame, ή να προσθέσουμε μια στήλη σε ένα data frame, ή να ενώσουμε δύο πλαίσια δεδομένων με τις εντολές cbind (κατά στήλες), rbind (κατά γραμμές).

my.col1<-1:10
my.col2<-rep(c(T,F),c(3,7))
my.dataframe<-data.frame(my.col1, my.col2)
my.dataframe
##    my.col1 my.col2
## 1        1    TRUE
## 2        2    TRUE
## 3        3    TRUE
## 4        4   FALSE
## 5        5   FALSE
## 6        6   FALSE
## 7        7   FALSE
## 8        8   FALSE
## 9        9   FALSE
## 10      10   FALSE
my.dataframe$my.col3<-runif(10)
my.dataframe
##    my.col1 my.col2   my.col3
## 1        1    TRUE 0.6354692
## 2        2    TRUE 0.2055832
## 3        3    TRUE 0.3799463
## 4        4   FALSE 0.6276473
## 5        5   FALSE 0.1082351
## 6        6   FALSE 0.2790581
## 7        7   FALSE 0.2635770
## 8        8   FALSE 0.5921390
## 9        9   FALSE 0.9259178
## 10      10   FALSE 0.2637132
A<-matrix(letters[1:20], nrow=10, ncol=2)
is.data.frame(A)
## [1] FALSE
A<-as.data.frame(A)
is.data.frame(A)
## [1] TRUE
my.dataframe<-cbind(my.dataframe,A)
my.dataframe
##    my.col1 my.col2   my.col3 V1 V2
## 1        1    TRUE 0.6354692  a  k
## 2        2    TRUE 0.2055832  b  l
## 3        3    TRUE 0.3799463  c  m
## 4        4   FALSE 0.6276473  d  n
## 5        5   FALSE 0.1082351  e  o
## 6        6   FALSE 0.2790581  f  p
## 7        7   FALSE 0.2635770  g  q
## 8        8   FALSE 0.5921390  h  r
## 9        9   FALSE 0.9259178  i  s
## 10      10   FALSE 0.2637132  j  t
colnames(my.dataframe)[4:5]<-c("my.col4","my.col5")
names(my.dataframe)
## [1] "my.col1" "my.col2" "my.col3" "my.col4" "my.col5"
write.csv(my.dataframe, "sergios_dataframe.csv")

Λίστες
Στα πλαίσια δεδομένων κάθε στήλη αντιστοιχεί σε μια μεταβλητή. Ακόμα και ο πίνακας A στο πιο πάνω παράδειγμα, μόλις ενταχθεί στο πλαίσιο δεδομένων χάνει την ιδιότητα του πίνακα και μετατρέπεται σε 2 μεταβλητές-στήλες. Οι λίστες επιτρέπουν το συνδυασμό αντικειμένων με διαφορετικές μορφές δεδομένων και οι οποίες μπορεί να βρίσκονται σε αντικέιμενα διαφορετικών τύπων. Συγκεκριμένα συνδυάζουν μεταβλητές οι οποίες δεν αντιστοιχούν απαραίτητα σε στήλες ίσου μήκους (πχ μπορεί μια μεταβλητή να είναι ένας αριθμός, άλλη να είναι 10x4 πίνακας, άλλη να είναι στήλη με 100 λέξεις κλπ). Ορίζονται με την εντολή list.

V1<-1:10
V2<-rep(c(T,F),c(2,3))
V3<-runif(1)
V4<-matrix(letters[1:20], nrow=5, ncol=4)
my.list<-list(my.var1=V1, my.var2=V2, my.var3=V3, my.var4=V4)
my.list
## $my.var1
##  [1]  1  2  3  4  5  6  7  8  9 10
## 
## $my.var2
## [1]  TRUE  TRUE FALSE FALSE FALSE
## 
## $my.var3
## [1] 0.8191457
## 
## $my.var4
##      [,1] [,2] [,3] [,4]
## [1,] "a"  "f"  "k"  "p" 
## [2,] "b"  "g"  "l"  "q" 
## [3,] "c"  "h"  "m"  "r" 
## [4,] "d"  "i"  "n"  "s" 
## [5,] "e"  "j"  "o"  "t"
names(my.list)
## [1] "my.var1" "my.var2" "my.var3" "my.var4"
my.list$my.var2
## [1]  TRUE  TRUE FALSE FALSE FALSE
my.list$my.var4[3,2]
## [1] "h"

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

2. Βασικές αρχές προγραμματισμού

Έχουμε δει διάφορες εντολές της R. Ένα πρόγραμμα στην R, είναι μια ακολουθία από εντολές σχεδιασμένες να λύνουν κάποιο συγκεκριμένο πρόβλημα όταν εκτελεστούν με τη σειρά. Για παράδειγμα θεωρήστε την ακολoυθία Fibonacci \(x_k=x_{k-1}+x_{k-2}, \;\forall k>2\). Οι πιο κάτω εντολές υπολογίζουν το \(x_6\):

x<-c()
x[1]<-1
x[2]<-1
x[3]<-x[2]+x[1]
x[4]<-x[3]+x[2]
x[5]<-x[4]+x[3]
x[6]<-x[5]+x[4]
cat("ο 6ος αριθμός Fibonacci είναι", x[6], "\n")
## ο 6ος αριθμός Fibonacci είναι 8

Η εντολή cat χρησιμεύει για την εκτύπωση συνδυασμού κειμένου και τιμών μεταβλητών.

2.1. Αποφυγή επαναλήψεων

Χρήση βρόγχων
Το πιο πάνω πρόγραμμα έχει μεγάλη επανάληψη και θα ήταν αδύνατο να υπολογίσουμε τον 100ο όρο της ακολουθίας Fibonacci με αυτό τον τρόπο. Με χρήση ενός βρόγχου “for” μπορούμε να υπολογίσουμε τους όρους της ακολουθίας πολύ πιο έξυπνα και οικονομικά.

n<-10
x<-c()
x[1]<-1
x[2]<-1
for(k in 3:n){
  x[k]<-x[k-1]+x[k-2]
}
cat("ο ", n, "ος αριθμός Fibonacci είναι ", x[n], "\n", sep="")
## ο 10ος αριθμός Fibonacci είναι 55

Ένας δεύτερος τύπος βρόγχου είναι ο “while”, που χρησιμοποιείται όταν ο αριθμός των επαναλήψεων δεν είναι εκ των προτέρων γνωστός. Για παράδειγμα μπορεί να χρησιμοποιηθεί για να βρεθεί ο πρώτος αριθμός Fibonacci που είναι μεγαλύτερος από το 500.

x<-c()
x[1]<-1
x[2]<-1
n<-2
while(x[n]<500){
  n<-n+1
  x[n]<-x[n-1]+x[n-2]
}
cat("ο 1ος αριθμός Fibonacci που είναι μεγαλύτερος από 500 είναι ο ", n, "ος και έχει τιμή ",  x[n], "\n", sep="")
## ο 1ος αριθμός Fibonacci που είναι μεγαλύτερος από 500 είναι ο 15ος και έχει τιμή 610

Εντολές υπό συνθήκη
Επιτρέπουν για παράδειγμα να εκτελούμε διαφορετικές εντολές σε διαφορετικές επαναλήψεις σε ένα βρόγχο. Για παράδειγμα, για να εμφανίσουμε όλους τους αριθμόυς \(n=1,2,\dots,20\) που είναι τέτοιοι ώστε \(cos(n)<0\) γράφουμε το εξής πρόγραμμα.

for(n in 1:20){
  if(cos(n)<0){
    cat(n, "\n")
  }
}
## 2 
## 3 
## 4 
## 8 
## 9 
## 10 
## 15 
## 16 
## 17

Μπορούμε να ζητήσουμε από το πρόγραμμα να εκτελέσει μια εντολή αν ισχύει μια συνθήκη και μια διαφορετική εντολή αν δεν ισχύει η συνθήκη.

for(n in 1:10){
  if(n%%2==0){
    cat(n, "is even\n")
  }
  else{
    cat(n, "is odd\n")
  }
}
## 1 is odd
## 2 is even
## 3 is odd
## 4 is even
## 5 is odd
## 6 is even
## 7 is odd
## 8 is even
## 9 is odd
## 10 is even

Εναλλακτικά το πιο πάνω πρόγραμμα μπορεί να γραφτεί πιο σύντομα με την συνθήκη ifelse

for(n in 1:10){
  cat(n, ifelse(n%%2==0, "is even", "is odd"), "\n")
}
## 1 is odd 
## 2 is even 
## 3 is odd 
## 4 is even 
## 5 is odd 
## 6 is even 
## 7 is odd 
## 8 is even 
## 9 is odd 
## 10 is even

Scripts εντολών
Μπορούμε να αποθηκεύσουμε τα προγράμματα μας σε αρχεία script (επιλέξτε πάνω δεξιά File/New File/ R Script) ούτως ώστε να τα επαναχρησιμοποιήσουμε στο μέλλον. Είναι καλή πρακτική να γράφουμε ευανάγνωστο κώδικα και σε αυτό βοηθά η χρήση σχολίων. Στην R γράφουμε σχόλια με χρήση του τελεστή “#”.

2.2. Διαίρει και βασίλευε

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

Στην R η αρχή του “Διαίρει και βασίλευε” επιτυγχάνεται με τη χρήση συναρτήσεων “functions”, οι οποίες επιτρέπουν τη δημιουργία νέων εντολών η κλήση των οποίων έχει ως αποτέλεσμα την εκτέλεση μιας σειράς άλλων εντολών.

fibonacci<- function(n){
  x<-c()
  x[1]<-1
  x[2]<-1
  for(k in 3:n){
    x[k]<-x[k-1]+x[k-2]
  }
  return(x[n])
}

Όταν εκτελέσουμε τις πιο πάνω εντολές δε γίνεται τίποτα. Οι εντολές αυτές απλά ορίζουν το όνομα fibonacci ως συντομογραφία για τις εντολές που υπάρχουν μεταξύ της πρώτης και τελευταίας αγκύλης. Η συνάρτηση fibonacci έχει ένα όρισμα (το n) και επιστρέφει τον όρο \(x_n\) της ακολουθίας Fibonacci (δηλαδή το x[n]). Κάθε φορά που θα καλούμε τη συνάρτηση fibonacci θα πρέπει να δίνουμε και μια τιμή για το όρισμα n. Οι συναρτήσεις μπορούν να έχουν όσα ορίσματα θέλουμε και όποιου τύπου θέλουμε. Μπορούν όμως να επιστρέφουν μόνο ένα αντικέιμενο. H εντολή return(x[n]) επιστρέφει το αντικέιμενο x[n] και τερματίζει τη συνάρτηση.

fibonacci(5)
## [1] 5
res<-fibonacci(10)
res
## [1] 55

Η πιο κάτω απλή συνάρτηση της R ορίζει τη μαθηματική συνάρτηση \(f(x)=\frac{1}{2\pi}e^{-\frac{x^2}2}\).

f<- function(x){
  return(1/(pi^2)*exp(-(x^2)/2))
}

Η ιδέα είναι ότι γράφουμε αυτές τις συναρτήσεις μόνο μια φορά και ακολούθως μπορούμε να τις χρησιμοποιήσουμε χωρίς να χρειάζεται να σκεφτούμε τις λεπτομέρειες της υλοποίησής τους. Έτσι μπορούν να αποτελούν δομικά μέρη μεγαλύτερων προγραμμάτων, σε συνδυασμό με άλλες συναρτήσεις που είναι διαθέσιμες εξαρχής στην R (sqrt, plot κλπ).

Οι μεταβλητές που χρησιμοποιούνται εντός της συνάρτησης, υπάρχουν μόνο εντός της συνάρτησης και δεν επηρεάζουν άλλες μεταβλητές εκτός της συνάρτησης που πιθανόν να έχουν το ίδιο όνομα.

n<-10
fibonacci(10)
## [1] 55
n
## [1] 10
x
##  [1]   1   1   2   3   5   8  13  21  34  55  89 144 233 377 610
y=fibonacci(10)
y
## [1] 55

Είναι χρήσιμο να βάζουμε σχόλια με τις περιγραφές των ορισμάτων μιας συνάρτησης (και τυχόν περιορισμούς που έχουν) καθώς επίσης και με περιγραφή του αντικειμένου που επιστρέφεται.

fibonacci<- function(n){
  # computes and returns n-th Fibonacci number
  # n is the order of the desired Fibonacci number, n>2
  # x[n] is the n-th Fibonacci number
  x<-c()
  x[1]<-1
  x[2]<-1
  for(k in 3:n){
    x[k]<-x[k-1]+x[k-2]
  }
  return(x[n])
}

Συχνά έχουμε περισσότερα από ένα ορίσματα σε μια συνάρτηση. Για παράδειγμα η συνάρτηση της R που δίνει δείγματα από την μονοδιάστατη κανονική κατανομή ορίζεται ως

rnorm<-function(n, mean, sd){
  # code for generating normal random numbers
  ...
}

Υπάρχουν 3 ορίσματα: n είναι το πλήθος των επιθυμητών δειγμάτων, mean ο μέσος και sd η τυπική απόκλιση. Με την εντολή rnorm(10,0,1) παίρνουμε 10 δείγματα από την τυπική κανονική κατανομή. Επειδή η ειδική περίπτωση mean=0 και sd=1 εμφανίζεται συχνά, έχει νόημα να βάλουμε τις τιμές αυτές ως προεπιλεγμένες εκτός αν ο χρήστης ζητήσει κάτι διαφορετικό στα ορίσματα. Ο πραγματικός ορισμός της rnorm είναι

rnorm<-function(n, mean=0, sd=1){
  # code for generating normal random numbers
  ...
}

To γεγονός ότι βάλαμε πχ mean=0 στα ορίσματα πιο πάνω, σημαίνει ότι η mean θα πάρει την τιμή 0 εκτός αν ο χρήστης δώσει κάποια άλλη τιμή στο όρισμα. Συνεπώς με την εντολή rnorm(10) παίρνουμε 10 δείγματα από την τυπική κανονική, με την rnorm(10,5) παίρνουμε 10 δείγματα από την N(5,1) κλπ. Προσοχή στο ότι το όρισμα n είναι απαραίτητο για να τρέξει η συνάρτηση. Επίσης αν θέλουμε η mean να κρατήσει την προεπιλεγμένη τιμής της αλλά να δώσουμε άλλη τιμή στην sd, γράφουμε rnorm(10,sd=3) ή rnorm(10, 0, 3).

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

2.3. Έλεγχος του κώδικα

Ελέγχουμε τον κώδικά μας για να αποφύγουμε λάθη και για να επιβεβαιώσουμε ότι ο κώδικας κάνει αυτό που επιθυμούμε να κάνουμε. Υπάρχουν 3 τύποι σφαλμάτων στον προγραμματισμό:

mean(c(1,2,,3))
mean(c(1,2,3)))
## Error: <text>:2:15: unexpected ')'
## 1: mean(c(1,2,,3))
## 2: mean(c(1,2,3)))
##                  ^

Αυτά τα σφάλματα είναι εύκολο να εντοπιστούν αφού συνήθως το μήνυμα που βγάζει η R είναι κατατοπιστικό.

print.var<-function(x){
  cat("η διακύμανση είναι", var(x), "\n")
}
print.var(c(1,2,3))
## η διακύμανση είναι 1
print.var(c())
## Error in var(x): 'x' is NULL

Αυτός ο τύπος σφαλμάτων είναι πιο δύσκολο να εντοπιστεί γιατί μπορεί το σφάλμα να μη συμβαίνει σε κάθε εκτέλεση του προγράμματος (πχ για κάποιους ορισμούς των παραμέτρων/ορισμάτων να εμφανίζεται και σε άλλους όχι).

“Arithmetic errors” είναι ειδική περίπτωση “Run-time errors” όπου το πρόγραμμα προσπαθεί να υπολογίσει εκφράσεις όπως 0/0 ή ρίζα αρνητικού αριθμού κλπ. Σε αυτή την περίπτωση η R θέτει την απάντηση σε NaN και βγάζει κάποια προειδοποίηση (συνήθως).

0/0
## [1] NaN
sqrt(-1)
## Warning in sqrt(-1): NaNs produced
## [1] NaN
average<-function(x){
  n<-length(x)
  s<-sum(x)
  return(x/n)
}

Η συνάρτηση δεν κάνει τη δουλειά που θέλουμε (γιατί επιστρέφει το x/n αντί το s/n), όμως δεν υπάρχει μήνυμα σφάλματος. Ο καλύτερος τρόπος για να εντοπιστεί αυτό το σφάλμα είναι καλώντας τη συνάρτηση και ελέγχοντας το αποτέλεσμα.

Τα σφάλματα σε προγράμματα ονομάζονται συχνά ‘bugs’ και η διαδικασία ανίχνευσής τους ‘debugging’. Μερικά βήματα που βοηθούν στον εντοπισμό σφαλμάτων:

Για παράδειγμα θέλουμε να ελέγξουμε τη συνάρτηση average. Πρώτα δοκιμάζουμε μερικές τιμές.

average(c(1,2,3))
## [1] 0.3333333 0.6666667 1.0000000

Προφανώς το αποτέλεσμα θα έπρεπε να είναι 2, οπότε βλέπουμε αμέσως ότι κάπου υπάρχει λάθος. Υποθέτοντας ότι δεν είδαμε ακόμα το λάθος στην εντολή return, μπορούμε να προσθέσουμε μια εντολή cat ως εξής:

average<-function(x){
  n<-length(x)
  s<-sum(x)
  cat("n=", n, " s=", s, "\n")
  return(x/n)
}
average(c(1,2,3))
## n= 3  s= 6
## [1] 0.3333333 0.6666667 1.0000000

Όταν ξανατρέξουμε τη συνάρτηση βλέπουμε ότι οι τιμές των n και s είναι αυτές που περιμέναμε. Περιορίσαμε με αυτό τον τρόπο την αναζήτηση του σφάλματος στην τελευταία γραμμή και τελικά μπορούμε να το εντοπίσουμε.

3. Παραγωγή τυχαίων αριθμών

Η R έχει ενσωματωμένες διάφορες συναρτήσεις για την παραγωγή (ψευδο-)τυχαίων αριθμών από διάφορες συνήθεις κατανομές. Υπάρχουν επίσης συναρτήσεις που υπολογίζουν τις συναρτήσεις κατανομής, της πυκνότητες καθώς και τα ποσοστημόρια αυτών των κατανομών.

Τα ονόματα αυτών των συναρτήσεων έχουν το εξής μοτίβο: το πρώτο γράμμα είναι

  • r για παραγωγή τυχαίων αριθμών

  • d για πυκνότητες (πιθανότητες για διακριτές κατανομές)

  • p για συναρτήσεις κατανομής

  • q για ποσοστημόρια.

Το υπόλοιπο του ονόματος καθορίζει την κατανομή.

Κατανομή Όνομα στην R
Διωνυμική κατανομή binom
\(\chi^2\) κατανομή chisq
Εκθετική κατανομή exp
Κατανομή Γάμμα gamma
Κανονική κατανομή norm
Κατανομή Poisson pois
Ομοιόμορφη κατανομή unif

Οι συναρτήσεις runif και rnorm παράγουν δείγματα από την ομοιόμορφη και την κανονική κατανομή αντίστοιχα. Πληκτρολογείστε ?rnorm για να δείτε τι ορίσματα παίρνουν. Στις συναρτήσεις που παράγουν τυχαίους αριθμούς, το πρώτο όρισμα είναι πάντα ο αριθμός των επιθυμητών δειγμάτων: πχ rnorm(10) παράγει 10 δείγματα από την Ν(0,1) ενώ rnorm(1,2,3) παράγει 1 δείγμα από την Ν(2,9) (προσοχή στο ότι το τρίτο όρισμα είναι η τυπική απόκλιση και όχι η διακύμανση).

Οι συναρτήσεις dnorm, dunif κλπ επιστρέφουν την τιμή της συνάρτησης πυκνότητας πιθανότητας σε κάποιο \(x\) που δίνουμε ως όρισμα. Αντίστοιχα οι συναρτήσεις pnorm, punif κλπ επιστρέφουν την τιμή της συνάρτησης κατανομής σε κάποιο \(x\). Τέλος οι συναρτήσεις qnorm, qunif κλπ με όρισμα \(\alpha\in[0,1]\) επιστρέφουν το σημείο \(x_\alpha\) που είναι τέτοιο ώστε \(P(X<x_\alpha)=\alpha\).

rnorm(10, mean=20, sd=3)
##  [1] 23.40341 21.79486 22.71167 17.74689 19.48714 19.07421 23.08807
##  [8] 18.00751 23.69586 16.21650
dexp(1, rate=2)
## [1] 0.2706706
pexp(1, rate=2)
## [1] 0.8646647
qnorm(0.95)
## [1] 1.644854

Μια εξαίρεση στον κανόνα των ονομάτων για τις συναρτήσεις που παράγουν τυχαίους αριθμούς, είναι η ομοιόμορφη διακριτή. Αν θέλουμε n δείγματα (με επανάθεση) από το \(\{1,2,\dots,a\}\) χρησιμοποιούμε την εντολή sample(a, n, replace=TRUE).

sample(5,10,replace=T)
##  [1] 3 5 2 3 5 4 5 2 3 3
sample(5,10,replace=F)
## Error in sample.int(x, size, replace, prob): cannot take a sample larger than the population when 'replace = FALSE'
sample(5,5, replace=F)
## [1] 1 4 5 3 2

Τέλος η εντολή set.seed ορίζει την αρχική τιμή της γεννήτριας τυχαίων αριθμών της R σε συγκεκριμένη τιμή (δείτε συζήτηση στη θεωρία).

4. Γραφικές δυνατότητες

Η R έχει πολύ ισχυρές γραφικές δυνατότητες, ένα δείγμα των οποίων φαίνεται πιο κάτω.

x<-rnorm(100)
plot(x, main="Δείγματα από Ν(0,1)")

y<-seq(-10,10,.2)
z<-sin(y)
plot(y,z, type='l', xlab="Άξονας των x", ylab="Άξονας των y")