naked_code

my low-level programming blog

home


Πρόλογος

Το παρακάτω είναι ένα πρόχειρο κείμενο σημειώσεων πρωτίστως προγραμματισμού χρησιμοποιώντας ως μέσο (κι όχι αυτοσκοπό) την εκμάθηση της R.

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

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

Μέσα στο κείμενο, στα θέματα που αφορούν γενικά τον προγραμματισμό, θα παρουσιάζω ενδιαφέρουσες πηγές για περαιτέρω μελέτη του εκάστοτε αντικειμένου. Όσον αφορά συγκεκριμένα την R, σε περίπτωση που χρειάζεστε διευκρινίσεις ή περισσότερες πληροφορίες, υπάρχει το Geeks for Geeks και το W3Schools με ενδιαφέροντα και λεπτομερέστερα λήμματα για κάθε πτυχή της γλώσσας.


Σύντομη εισαγωγή στον προγραμματισμό

Η λειτουργία ενός υπολογιστή ανάγεται σε εντολές “άσσων και μηδενικών” (δεν έχει σημασία ακριβώς το τι σημαίνει αυτό για την ώρα). Αυτές οι εντολές κατευθύνουν με ακρίβεια την λειτουργία του επεξεργαστή και άλλων εξαρτημάτων.

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

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

Το κυριότερο από αυτά τα εργαλεία, είναι ο μεταγλωττιστής (compiler), ο οποίος δέχεται τις εντολές που είναι κατανοητές στον άνθρωπο και τις μεταφράζει σε εντολές που γίνονται “κατανοητές” από τον υπολιγιστή. Για περαιτερω μελετη.

Οι scripting γλώσσες, όπως η R και η Python, δεν μεταφράζουν (συνήθως - καθώς πρόκειται για σύμβαση) ολόκληρο το πρόγραμμα όπως περιγράφεται παραπάνω, αλλά μεταγλωττίζουν μια-μια τις εντολές του προγράμματος, όχι με την σειρά που είναι γραμμένες αριθμητικά, αλλά με την λογική σειρά της εκτέλεσης του αρχείου.
Χρησιμοποιούν δηλαδή έναν διερμηνέα (interpreter). Για περαιτερω μελετη.


Τιμές, μεταβλητές & σταθερές - Values, Variables & constants

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

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

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

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

Τα ονόματα μεταβλητών μπορεί να περιέχουν γράμματα, ψηφία, τελείες και κάτω παύλες. Μπορούν να ξεκινάνε μόνο με γράμματα και τελείες. Αν ξεκινάνε με τελεία, απαγορέυεται ο πρώτος χαρακτήρας που δεν είναι τελεία να είναι αριθμός. Έχουν σημασία τα κεφαλαία και τα μικρά γράμμτα. Το a_variable είναι διαφορετικό όνομα μεταβλητής από το A_Variable. Τέλος, υπάρχουν κάποιες προκαθορισμένες λέξεις από την R, που δεν μπορούν να χρησιμοποιηθούν στα δικά μας προγράμματα.

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

Επειδή οι μεταβλητές δεν μπορούν να περιέχουν κενά, δηλαδή το my interesting data δεν είναι έγκυρο όνομα μεταβλητής. Όταν θέλουμε οι μεταβλητές να περιέχουν δύο ή παραπάνω λέξεις, υπάρχουν δύο μεγάλες “σχολές” συμβάσεων που μπορούμε να ακολουθήσουμε (χωρίς να είναι απαραίτητο)

  1. Camel Case, στην οποία χωρίζουμε τις λέξεις βάζοντας κεφαλαίο γράμμα στην αρχή της κάθε διαφορετικής λέξης (συνήθως η πρώτη λέξη ξεκινάει με μικρό γράμμα), δηλαδή myInterestingData
  2. Snake case, στην οποία οι λέξεις χωρίζονται με κάτω παύλα, δηλαδή my_interesting_data

Για περαιτερω μελετη.

Ορισμός μεταβλητών - Variable assignment

Στην R, αποδίδουμε μια τιμή σε μια μεταβλητή απ’ ευθείας, χωρίς να χρειαστεί να την ορίσουμε προηγουμένως.

my_variable = 10 ή my_variable <- 10

Χρησιμοποιούμε το = ή το <-, την διαφορά μεταξύ των δύο θα την αναλύσουμε στο κεφάλαιο σχετικά με τους τελεστές.

Είδη μεταβλητών - Types of Variables

Μπορούμε να χρησιμοποιήσουμε την συνάρτηση class() ώστε να εξακριβώσουμε το είδος κάποιας μεταβλητής

Boolean ή Logical

Οι μεταβλητές Boolean (μεταβλητές άλγεβρας Boole), είναι οι απλούστερες μεταβλητές στις οποίες αποθηκεύεται αποκλειστικά η πληροφορία αληθές (true) ή ψευδές (false)

bool_var = FALSE
print(bool_var)
## [1] FALSE
print(class(bool_var))
## [1] "logical"
Ακέραιοι - Integers

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

x = 12L
print(x)
## [1] 12
print(class(x))
## [1] "integer"
Μη ακέραιοι - Numeric

Είναι το πιο συχνό είδος μεταβλητών.

x = 12.5
print(x)
## [1] 12.5
print(class(x))
## [1] "numeric"
Μιγαδικοί - Complex

Στην R υπάρχουν μεταβλητές που μπορούν να αποθηκεύσουν μιγαδικούς αριθμούς. Οι μιγαδικοί αριθμοί είναι πραγματικοί αριθμοί που έχουν ένα φανταστικό μέρος (z = x +iy, με i να είναι η φανταστική μονάδα)

z = 1 + 2i
print(z)
## [1] 1+2i
print(class(z))
## [1] "complex"
Χαρακτήρες και σειρές χαρακτήρων - Characters and string of characters

Μπορούμε να χρησιμοποιούμε μονά '' ή διπλά "" εισαγωγικά

one_character = "a"
str = 'abcdefg'
print(one_character)
## [1] "a"
print(class(one_character))
## [1] "character"
print(str)
## [1] "abcdefg"
print(class(str))
## [1] "character"
Άλλες χρήσιμες σχετικές συναρτήσεις

Η συνάρτηση length() επιστρέφει το μήκος της τιμής (ή της τιμής που περιέχεται σε μεταβλητή)

Η attributes() επιστρέφει διάφορα άλλα μεταδεδομένα που μπορεί να αφορούν μια μεταβλητή

Μετατροπή

Στην R, μπορούμε να μετατρέψουμε μια μεταβλητή από τη μία μορφή στην άλλη, χρησιμοποιώντας ορισμένες συναρτήσεις.

as.logical(c(1,0))
## [1]  TRUE FALSE
as.numeric(c(TRUE, FALSE))
## [1] 1 0
as.character(c(1,2,3))
## [1] "1" "2" "3"

Εμφάνιση - Print

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

print(10)
## [1] 10
x <- 20
print(x)
## [1] 20

Μέσα στην print(), χρησιμοποιούμε την paste() ώστε να “κολλήσουμε” μαζί διαφορετικά μέλη.

print(paste("variable x is equal to:", x))
## [1] "variable x is equal to: 20"

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

x
## [1] 20

Είσοδος - Input

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

Χρησιμοποιούμε την συνάρτηση readline() .

Σχόλια - Comments

Χρησιμοποιούμε το # σε κάποιο σημείο μιας γραμμής του προγράμματος, ώστε να δηλώσουμε πως αυτό που ακολουθεί είναι σχόλιο (και συνεπώς δεν εκτελείται - για την ακρίβεια αγνοείται - από τον compiler)

a <- 10 #this assigns a to 10
print(a) #this prints a
## [1] 10
#the command below is being ignored by the compiler
#a <- 20

#so a remains equal to 10
print(a)
## [1] 10

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

Σπανιότερα, μπορούμε να χρησιμοποιήσουμε τα σχόλια στην αποσφαλμάτωση (debugging), ώστε να προσπεράσουμε κάποια εντολή (χωρίς να την σβήσουμε εντελώς) και να δοκιμάσουμε πού υπάρχει σφάλμα στον κώδικα.

Τελεστές - Operators

Οι τελεστές χρησιμοποιούνται για πράξεις και άλλες διεργασίες σε μεταβλητές και τιμές.

Αριθμητικοί τελεστές - Arithmetic Operators
Τελεστής Περιγραφή Παράδειγμα
+ πρόσθεση a + b
- αφαίρεση a - b
* πολλαπλασιασμός a * b
/ διαίρεση a / b
^ εκθέτης a ^ b
%% υπόλοιπο διαίρεσης a %% b
%/% ακέραια διαίρεση a %/% b
Τελεστές σύγκρισης - Comparison Operators
Τελεστής Περιγραφή Παράδειγμα
== ισότητα a == b
!= ανισότητα a != b
> μεγαλύτερο a > b
< μικρότερο a < b
<= μεγαλύτερο ή ίσο a <= b
>= μικρότερο ή ίσο a >= b
Λογικοί τελεστές - Logical Operators
Τελεστής Περιγραφή Παράδειγμα
& Λογικός τελεστής σύζευξης - AND (επιστρέφει αληθές αν Α και Β αληθή - χρησιμοποιείται και για σύγκριση διανυσμάτων)

x <- c(TRUE, FALSE)y <- c(TRUE, TRUE)

print(x & y)

: TRUE FALSE

&& Λογικός τελεστής σύζευξης - AND (επιστρέφει αληθές αν Α και Β αληθή - δεν χρησιμοποιείται και για σύγκριση διανυσμάτων)

x = TRUE

print((2 < 0) && x)

: FALSE

| Λογικός τελεστής διάζευξης - OR (επιστρέφει αληθές αν Α ή Β αληθή - χρησιμοποιείται και για σύγκριση διανυσμάτων)

x <- c(TRUE, FALSE)y <- c(TRUE, TRUE)

print(x | y)

: TRUE TRUE

|| Λογικός τελεστής διάζευξης - OR (επιστρέφει αληθές αν Α ή Β αληθή - δεν χρησιμοποιείται και για σύγκριση διανυσμάτων)

x = TRUE

print((2 < 0) || x)

: TRUE

! Λογικός τελεστής άρνησης - NOT (επιστρέφει αληθές αν Α είναι ψευδές)

print(!TRUE)

: FALSE

Άλλοι τελεστές
Τελεστής Περιγραφή Παράδειγμα
: Δημιουργεί σειρά αριθμών

x = 1:5

(που σημαίνει x = c(1,2,3,4,5 )

%in% Έλεγχος για την ύπαρξη στοιχείου σε διάνυσμα x %in% y
%*% Πολλαπλασιασμός πινάκων Matrix1 %*% Matrix2

Για το τέλος αφήνουμε τους τελεστές ανάθεσης. Αυτοί χρησιμοποιούνται ώστε να θέσουμε την τιμή μιας μεταβλητής, να ορίσουμε μια συνάρτηση ή μια παράμετρο συνάρτησης.

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

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

Το <- δεν μπορεί να χρησιμοποιηθεί για ορισμό μεταβλητών εντός του πεδίου (scope) μιας συνάρτησης. Για εκτενέστερη εξήγηση σχετικά με τα scopes, μπορείτε να δείτε την σχετική υποενότητα για τις συναρτήσεις.

Για περαιτερω μελετη.

Δομές δεδομένων - Data structures

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

Η σειρά χαρακτήρων (string), για παράδειγμα, είναι μια απλοϊκή δομή δεδομένων που αποθηκεύει μια λίστα χαρακτήρων στην οποία ο ένας χαρακτήρας διαδέχεται τον άλλον. Η χρήση της είναι τόσο συχνά, που η R την αντιμετωπίζει σαν μια ακόμα μορφή τιμών. Αυτό είναι θέμα σύμβασης και δεν ισχύει σε άλλες γλώσσες προγραμματισμού απαραίτητα (βλ. C char variable)

Διάνυσμα - Vector

Είναι η πιο συνηθισμένη δομή δεδομένων στην R. Δημιουργούνται με την συνάρτηση vector() ή (συχνότερα) με την συνάρτηση c().

Υπάρχουν δύο είδη vectors. Τα atomic vectors, περιέχουν κάποιο απ’ τα είδη τιμών, αλλά μόνο το ίδιο. Τα recursive vectors μπορούν να περιέχουν διαφορετικά είδη τιμών και δεδομένων.

vector1 = c() #same as "vector1 = vector()"
vector2 = c("numeric", length = 10) #vectors can have pre-defined length and datatype
vector3 = c(2,1,4,3,2)
vector4 = c("ab","cd","hi")

print(vector3)
## [1] 2 1 4 3 2

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

print(vector3 ** 2) #every element is squared
## [1]  4  1 16  9  4
print(vector3 + 0.2)
## [1] 2.2 1.2 4.2 3.2 2.2

Ορισμένες χρήσιμες συναρτήσεις:

print(max(vector3)) #maximum
## [1] 4
print(min(vector3)) #minimum
## [1] 1
print(mean(vector3)) #arithmetic average
## [1] 2.4
print(class(vector3))
## [1] "numeric"
print(length(vector3)) #length/size of the vector
## [1] 5
print(str(vector3)) #internal stracture of vector
##  num [1:5] 2 1 4 3 2
## NULL
print(sort(vector3)) #returns the vector sorted
## [1] 1 2 2 3 4
print(rev(vector3)) #returns the vector inversed
## [1] 2 3 4 1 2
print(unique(vector3)) #returns the unique values of the vector
## [1] 2 1 4 3

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

Επιλογή με βάση την θέση. Αξίζει να σημειωθεί ότι στην R, οι δείκτες θέσης των διανυσμάτων ξεκινούν από το 1. Το πρώτο στοιχείο του διανύσματος είναι το vector[1], το δεύτερο είναι το vector[2], κοκ. Στις περισσότερες γλώσσες προγραμματισμού (Python, το πρώτο στοιχείο έχει δείκτη το 0. Αυτό είναι φυσικά θέμα σύμβασης. Για περαιτέρω μελέτη.

vector3[3] #the third element
## [1] 4
vector3[-3] #everything BUT the third element
## [1] 2 1 3 2
vector3[1:3] #elements one to three
## [1] 2 1 4
vector3[-(1:3)] #all elements EXCEPT those one to three
## [1] 3 2
vector3[c(1,3)] #elements one and three
## [1] 2 4

Επιλογή με βάση την τιμή

vector3[vector3 == 2] #all elements equal to two
## [1] 2 2
vector3[vector3 < 3] #all elements less than three
## [1] 2 1 2
vector3[vector3 %in% c(1,2,5)] #elements in the set 1,2,5
## [1] 2 1 2
vector3["a"] #elements with name "ab"
## [1] NA

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

Για παράδειγμα, μπορούμε να προσθέσουμε στοιχεία στο διάνυσμα. Η συνάρτηση append() επιστρέφει ένα νέο vector, το οποίο

vector3 <- append(vector3, 10, 5) #vector3 is the vector to append, 10 is the appended value, 5 (optional) is the index of the appended item

Πίνακας - Matrix

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

Οι πίνακες χρησιμοποιούν (και περιέχουν) vectors για την αποθήκευση των δεδομένων.

Ορισμός πίνακα:

matrix(vector, nrow, ncol, byrow = FALSE)

  • vector: το διάνυσμα δεδομένων με το οποίο θα γεμίσουμε τον πίνακα

  • nrow: αριθμός των σειρών του πίνακα

  • ncol: αριθμός των στηλών του πίνακα

  • byrow: μπορούμε να μην το ορίσουμε, από μόνο του είναι ορισμένο σε FALSE. Αν είναι ορισμένο TRUE, τότε ο πίνακας δεν γεμίζει ανά στήλη, αλλά ανά σειρά.

#creating a 3x2 matrix and filling it with 1,2,3,4,5,6
matrix1 = matrix(c(1,2,3,4,5,6), nrow = 3, ncol = 2)
print(matrix1)
##      [,1] [,2]
## [1,]    1    4
## [2,]    2    5
## [3,]    3    6
#creating a 2x3 matrix and filling it with 1,2,3,4,5,6 BY ROW
matrix2 = matrix(c(1,2,3,4,5,6), nrow = 2, ncol = 3, byrow = TRUE)
print(matrix2)
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6

Η πρόσβαση σε στοιχεία του πίνακα γίνεται ως εξής: matrix[x, y] , όπου x είναι ο αριθμός της σειράς και y ο αριθμός της στήλης στην οποία βρίσκεται το στοιχείο που θέλουμε να δούμε/επεξεργαστούμε/σβήσουμε.

print(matrix1[3,2])
## [1] 6
print(matrix2[1,3])
## [1] 3

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

print(matrix1[, 2])
## [1] 4 5 6
print(matrix2[1, ])
## [1] 1 2 3

Μπορούμε να υπολογίσουμε τον ανάστροφο (transposed) πίνακα με την t()

t(matrix1)
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6

Πολλαπλασιάζουμε πίνακες με τον τελεστή %*% , όπως είδαμε και παραπάνω

matrix1 %*% matrix2
##      [,1] [,2] [,3]
## [1,]   17   22   27
## [2,]   22   29   36
## [3,]   27   36   45

Εξαιρετικά χρήσιμη είναι η συνάρτηση solve(m, n) για την επίλυση της εξίσωσης m * x = n . Συνήθως χρησιμοποιείται για την εύρεση του αντιστρόφου πίνακα, όταν το n μείνει κενό.

square_matrix = matrix(c(-1, 1, 3/2, -1), nrow = 2, ncol = 2)
solve(square_matrix)
##      [,1] [,2]
## [1,]    2    3
## [2,]    2    2

Λίστα - List

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

Κάποιος υπερασπιστής της R θα έλεγε ότι οι λίστες είναι recursive vectors (αναδρομικά διανύσματα), επειδή μπορούν να αποθηκεύουν υπο-λίστες. Ωστόσο, το ίδιο μπορούν να κάνουν ΚΑΙ τα vectors (ή τουλάχιστον, ίδια είναι η χρήση ακόμη κι αν η κατηγοριοποίηση είναι διαφορετική για την γλώσσα προγραμματισμού).

vector1 = c(c(1,2,3), 2, c(c(c(c(c(1))))), c("r is so fun!", "yes"))
print(vector1)
## [1] "1"            "2"            "3"            "2"            "1"           
## [6] "r is so fun!" "yes"

Πολύ συνοπτικά,

#creating a list
#x,y,z are the names of the elements (same can be done with vectors)
l <- list(x = 1, y = 2, z = 3)
print(l[[2]])
## [1] 2
print(l$z)
## [1] 3
print(l['y'])
## $y
## [1] 2

Για περαιτερω μελετη.

Διάταξη - Array

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

array3d <- array(c(1:24), dim = c(4, 3, 2))
print(array3d)
## , , 1
## 
##      [,1] [,2] [,3]
## [1,]    1    5    9
## [2,]    2    6   10
## [3,]    3    7   11
## [4,]    4    8   12
## 
## , , 2
## 
##      [,1] [,2] [,3]
## [1,]   13   17   21
## [2,]   14   18   22
## [3,]   15   19   23
## [4,]   16   20   24

Για περαιτερω μελετη.

Πλαίσιο δεδομένων - Data frame

Το Data Frame χρησιμοποιείται όπως ο πίνακας. Είναι μια 2x2 διάταξη δεδομένων που μπορεί να περιέχει διανύσματα ως στήλες και σειρές, τα οποία διανύσματα μπορούν να έχουν διαφορετικούς τύπους δεδομένων.

my_dataframe <- data.frame(
    first_col  = c(val1, val2, ...),
    second_col = c(val1, val2, ...),
    ... )

Για παράδειγμα:

eklogikos_katalogos <- data.frame (
  Name = c("Γεώργιος Μπίθας", "Αλίκη Περπέρογλου", "Αναστάσιος Μήτσος"),
  Age = c(54, 21, 15),
  Able_to_vote = c(TRUE, TRUE, FALSE)
)

print(eklogikos_katalogos)
##                Name Age Able_to_vote
## 1   Γεώργιος Μπίθας  54         TRUE
## 2 Αλίκη Περπέρογλου  21         TRUE
## 3 Αναστάσιος Μήτσος  15        FALSE

Μπορούμε να διαβάσουμε συγκεκριμένες στήλες από το data frame.

Για παράδειγμα, αν μας ενδιαφέρουν μόνο τα ονόματα του εκλογικού καταλόγου:

print(eklogikos_katalogos[1]) #an gnwrizoume oti to onoma einai h prwth sthlh
##                Name
## 1   Γεώργιος Μπίθας
## 2 Αλίκη Περπέρογλου
## 3 Αναστάσιος Μήτσος
print(eklogikos_katalogos[["Name"]]) #an den gnwrizoume poia sthlh periexei to onoma
## [1] "Γεώργιος Μπίθας"   "Αλίκη Περπέρογλου" "Αναστάσιος Μήτσος"
print(eklogikos_katalogos$Name) #diaforetikos tropos grafhs tou prohgoumenou
## [1] "Γεώργιος Μπίθας"   "Αλίκη Περπέρογλου" "Αναστάσιος Μήτσος"

Καλό είναι να γνωρίζουμε, ότι απ’ την παραπάνω υλοποίηση το eklogikos_katalogos[1] επιστρέφει list ενώ τα άλλα δύο επιστρέφουν character.

Μπορούμε επίσης να προσθέσουμε σειρές με την rbind()

eklogikos_katalogos <- rbind(eklogikos_katalogos,
                             c("Ιωάννης Χαραλάμπους", 25, TRUE))
print(eklogikos_katalogos)
##                  Name Age Able_to_vote
## 1     Γεώργιος Μπίθας  54         TRUE
## 2   Αλίκη Περπέρογλου  21         TRUE
## 3   Αναστάσιος Μήτσος  15        FALSE
## 4 Ιωάννης Χαραλάμπους  25         TRUE

Αντίστοιχα, προσθέτουμε στήλες με την cbind()

eklogikos_katalogos <- cbind(eklogikos_katalogos, District = 
                                 c("Attiki", "Ioannina", "Thessaloniki", "Chania"))
print(eklogikos_katalogos)
##                  Name Age Able_to_vote     District
## 1     Γεώργιος Μπίθας  54         TRUE       Attiki
## 2   Αλίκη Περπέρογλου  21         TRUE     Ioannina
## 3   Αναστάσιος Μήτσος  15        FALSE Thessaloniki
## 4 Ιωάννης Χαραλάμπους  25         TRUE       Chania

Μια χρήσιμη συνάρτηση για τα Data Frames είναι η summary() , η οποία επιστρέφει διάφορα σημαντικά δεδομένα για το πλαίσιο δεδομένων.

print(summary(eklogikos_katalogos))
##      Name               Age            Able_to_vote         District        
##  Length:4           Length:4           Length:4           Length:4          
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character

Για περαιτερω μελετη.

Έλεγχος Ροής - Control flow

Ένα απ’ τα πιο σημαντικά εργαλεία στον προγραμματισμό, είναι οι εντολές που ελέγχουν την ροή ενός προγράμματος.

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

Στην πρώτη κατηγορία, ένας όρος ελέγχει μια πρόταση. Αν αυτή είναι αληθής (πχ 2 == 2 -> TRUE ), τότε η εντολή (ή το μπλοκ εντολών) που περιέχονται στον έλεγχο εκτελούνται από το πρόγραμμα.

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

Πρόταση υπό όρους - If statement

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

x <- 1

if (x == 1) {
    print("x is equal to 1")
}
## [1] "x is equal to 1"
if (x == 2) {
    print("x is equal to 2")
}

Υπάρχει επίσης το else if που χρησιμοποιείται μετά το if ώστε να εξετάσουμε άλλους όρους, εφόσον το πρώτο if δεν ισχύει. Το else χωρίς να ακολουθεί if εκτελείται σε περίπτωση που δεν ισχύει κανένα από τα προηγούμενα statements. Μπορούμε να χρησιμοποιούμε περισσότερα από ένα else if ανά έλεγχο.

x <- 1
if (x < 0) {
    print ("x is negative")
} else if (x > 0) {
    print ("x is positive")
} else {
    print ("x is equal to zero")
}
## [1] "x is positive"
fruit <- "banana"

if (fruit == "apple") {
    print ("the fruit is an apple")
} else if (fruit == "peach") {
    print ("the fruit is a peach")
} else if (fruit == "pear") {
    print ("the fruit is a pear")
} else if (fruit == "orange") {
    print ("the fruit is an orange")
} else {
    print ("unknown fruit")
}
## [1] "unknown fruit"

Για περαιτερω μελετη.

Δομές επανάληψης (λούπες) - Loops

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

Λούπα όσο - While loop

Το πιο βασικό είδος λούπας. Λειτουργεί όπως το if, ωστόσο το μπλοκ εντολών εκτελείται όσο ο έλεγχος ισχύει. Ο έλεγχος δοκιμάζεται κάθε φορά πριν τρέξει το μπλοκ εντολών.

i <- 0

while (i < 5) {
    i <- i + 1
    print (i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
Λούπα για - For loop

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

for (x in 1:5) {
    print (x)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
dice <- c(1, 2, 3, 4, 5, 6)

for (number in dice) {
  print(number)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
animals <- c('cat', 'dog', 'komodo dragon')

for (animal in animals) {
    print (animal)
}
## [1] "cat"
## [1] "dog"
## [1] "komodo dragon"
Εντολές που χρησιμοποιούνται σε λούπες

Υπάρχουν δύο βασικές εντολές που μπορούμε να χρησιμοποιήσουμε για τον έλεγχο μιας λούπας.

Η πρώτη είναι η break . Όταν (και αν) το πρόγραμμα φτάσει στο break , τότε η λούπα σταματάει (“σπάει”) και το πρόγραμμα προχωράει μετά την λούπα.

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

fruits <- list("apple", "banana", "cherry")

for (x in fruits) {
  if (x == "cherry") {
    break
  }
  print(x)
}
## [1] "apple"
## [1] "banana"
fruits <- list("apple", "banana", "cherry")

for (x in fruits) {
  if (x == "banana") {
    next
  }
  print(x)
}
## [1] "apple"
## [1] "cherry"

Για περαιτερω μελετη.

Συναρτήσεις - Functions

Ορισμός και βασικές λειτουργίες

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

  1. μπορεί να λάβει παραμέτρους
  2. εκτελείται όταν καλείται
  3. μπορεί να επιστρέφει κάποια τιμή

Για περαιτερω μελετη.

Υπάρχουν ορισμένες βασικές συναρτήσεις που συμπεριλαμβάνονται στην γλώσσα. Κάποιες από αυτές τις έχουμε συναντήσει ήδη (το print , το length κ.α.)

Είναι πολύ συχνό το να χρειαζόμαστε κάποια συνάρτηση η οποία δεν υπάρχει ούτε στην γλώσσα προραμματισμού (built-in function), ούτε σε κάποια βιβλιοθήκη που μπορούμε να κατεβάσουμε από το ίντερνετ (library).

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

Δημιουργούμε μια συνάρτηση ως εξής:

my_function <- function() {
    print("My first function!")
}

Καλούμε την συνάρτηση με τον γνωστό τρόπο:

my_function()
## [1] "My first function!"

Αρκετές φορές, χρειάζεται να περάσουμε παραμέτρους και να επιστρέψουμε τιμές χρησιμοποιώντας μία συνάρτηση.

Ας δούμε, για παράδειγμα, πώς θα υλοποιούσαμε μία απλή συνάρτηση που ελέγχει έναν αριθμό. Αν αυτός είναι άρτιος/ζυγός, επιστρέφει TRUE . Αν είναι περιττός/μονός, επιστρέφει FALSE .

#etsi leme sto programma oti h synarthsh perimenei mia parametro
is_even <- function(number) {
    #an o arithmos diaireitai teleia me to 2
    if (number %% 2 == 0) {
        #tote epistrefoume TRUE
        return (TRUE)
    #an oxi
    } else {
        #tote epistrefoume FALSE
        return (FALSE)
    }
}

print(is_even(5))
## [1] FALSE
print(is_even(10))
## [1] TRUE

Κάποιες φορές, χρειάζεται να περάσουμε παραπάνω από μία παράμετρο. Διαχωρίζουμε τις παραμέτρους με , , τόσο στον ορισμό της συνάρτησης, όσο και στην κλήση της.

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

same_first_letter <- function(firstname, lastname) {
    if (substr(firstname, 1, 1) == substr(lastname, 1, 1)) {
        return (TRUE)
    } else {
        return (FALSE)
    }
}

print(same_first_letter("Mixalis", "Mimikos"))
## [1] TRUE
print(same_first_letter("Charalampos", "Lamprou"))
## [1] FALSE

Αντίστοιχα φυσικά μπορούμε να υλοποιήσουμε συναρτήσεις με 3, 4, 5 κ.ο.κ. παραμέτρους.

Συχνά είναι χρήσιμο για την ευκολία της κλήσης της συνάρτησης είναι να ορίζουμε κάποιες προεπιλεγμένες τιμές των παραμέτρων (default values). Δηλαδή,

#edw orizoume tis default times
same_first_letter <- function(firstname = "John", lastname = "Doe") {
    if (substr(firstname, 1, 1) == substr(lastname, 1, 1)) {
        return (TRUE)
    } else {
        return (FALSE)
    }
}

print(same_first_letter())
## [1] FALSE

Έτσι η same_first_letter εκτελείται με τις προεπιλεγμένες τιμές John Doe . Επιστρέφει FALSE εφόσον φυσικά δεν αρχίζουν απ’ το ίδιο γράμμα.

Αναδρομή - Recursion

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

Για περαιτερω μελετη.

Για να είναι επιτυχημένος ένας αναδρομικός αλγόριθμος, υπάρχουν 3 κανόνες που δεχόμαστε σαν αξιώματα.

  1. πρέπει να υπάρχει ένας βασικός έλεγχος - base case
  2. ο αλγόριθμος πρέπει να κινείται προς τον βασικό έλεγχο
  3. ο αλγόριθμος πρέπει να καλεί τον εαυτό του.

Επειδή η αναδρομή (recursion) είναι - συνήθως - πιο δυσνόητη από τις λούπες που παρουσιάσαμε πριν (iteration), είναι σημαντικό να γνωρίζουμε πότε είναι χρήσιμη.

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

Η αναδρομή χρησιμοποιείται στην βάση του διαίρει και βασίλευε. Είναι άξια χρήσης όταν το μεγάλο πρόβλημα ανάγεται σε μικρότερα, επαναλαμβανόμενα προβλήματα. Λύνοντας τα μικρά προβλήματα, βρίσκουμε την λύση στο μεγάλο.

’Ενα χαρακτηριστικό παράδειγμα είναι ο υπολογισμός αριθμών που εμπίπτουν στην ακολουθία Φιμπονάτσι.

Οι αριθμοί Φιμπονάτσι είναι η ακολουθία αυτής της μορφής:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ... , ...

Παρατηρούμε ότι κάθε αριθμός είναι το άθροισμα των δύο προηγούμενων.

Πληροφοριακά, το όριο του πηλίκου ενός αριθμού Φιμπονάτσι διά τον προηγούμενο στο +∞ ισούται με φ, την λεγόμενη χρυσή τομή.

\(lim_{n⤍+∞}{F_{n+1}\over{F_n}} = φ ≅ 1.61803\)

Έστω ότι θέλουμε να γράψουμε μία συνάρτηση που υπολογίζει τον ν-οστό αριθμό της ακολουθίας. Προφανώς η συνάρτηση χρειάζεται ως παράμετρο τον αριθμό n .

fib <- function(n) {}

Χρειαζόμαστε επίσης ένα base-case, ένα σημείο στο οποίο ο αλγόριθμος σταματάει να καλεί τον εαυτό του.

Αν παρατηρήσουμε τους πρώτους αριθμούς της ακολουθίας σε σχέση με την θέση (μετρώντας απ’ το 0), βλέπουμε το εξής:

ΤΙΜΕΣ: 0, 1, 1

ΘΕΣΗ: 0 1 2

Όταν η θέση είναι 1 ή 0 , δηλαδή όταν n == 0 ή n == 1 , τότε ο αριθμός της τιμής που αντιστοιχεί σε αυτές τις θέσεις n , είναι ο ίδιος με την θέση.

Άρα το base-case μας μπορεί να είναι

if (n <= 1) { return n }

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

Θα σχεδιάσουμε το πρόγραμμα ώστε όταν του ζητάμε έναν αριθμό, της ακολουθίας, θα ξεκινάει από αυτόν και θα προσθέτει τους δύο προηγούμενους μέχρι να φτάσει στο base-case.

Δηλαδή,

Το fib(5) (για παράδειγμα) προσπαθεί να υπολογίσει τον εαυτό του, κι έτσι καλεί το άθροισμα των δύο προηγούμενων, fib(4) και fib(3) (έτσι ορίσαμε τους αριθμούς Φιμπονάτσι).

Ενδεικτικά ας ακολουθήσουμε την πορεία του γράφου προς τα δεξιά. Για να υπολογιστεί το fib(3) , η συνάρτηση καλεί και αθροίζει τους δύο προηγούμενους, fib(2) και fib(1) .

Ο fib(2) αντίστοιχα καλεί αθροίζοντας τους fib(1) και fib(0) .

Κι όπως βλέπουμε, φτάσαμε επιτέλους στο base-case. Τα fib(1) και fib(0) είναι γνωστά, οπότε το πρόγραμμα σταματάει τους υπολογισμούς, αθροίζει πλέον όλους τους γνωστούς αριθμούς μέχρι να φτάσει δενδρωδώς στο fib(5) .

Το τελικό πρόγραμμα θα ήταν το εξής:

fib <- function(n) {
    if (n <= 1) return (n)
    else return (fib(n-1) + fib(n-2))
}

print(fib(4))
## [1] 3
print(fib(6))
## [1] 8
print(fib(10))
## [1] 55

Για περαιτερω μελετη.

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

Για περαιτερω μελετη.

Μαθηματικά στην R

Αξίζει να γίνει μια σύντομη αναφορά σε συναρτήσεις μαθηματικού ενδιαφέροντος που μπορούμε να χρησιμοποιήσουμε στην R

log(x) φυσικός λογάριθμος
exp(x) φυσικός εκθέτης
sqrt(x) τετραγωνική ρίζα
abs(x) απόλυτη τιμή
max(A) μέγιστο στοιχείο του Α
min(Α) ελάχιστο στοιχείο του Α
ceiling(x) αφαίρεση δεκαδικού μέρους + 1
floor(x) αφαίρεση του δεκαδικού μέρους
round(x, n) στρογγυλοποίηση του x σε n δεκαδικά
signif(x, n) στρογγυλοποίηση του x με στα n σημαντικότερα ψηφία
cor(x, y) δείκτης συνάφειας του x με το y
sum(Α) άθροισμα
mean(x) μέσος όρος
median(x) μεσαία τιμή
quantile(x) ποσοστημόριο
rank(Α) τάξη πίνακα
var(x) απόκλιση
sd(x) κανονική απόκλιση

Αλληλεπίδραση με αρχεία - Reading/writing in files

Σε περίπτωση που το πρόγραμμά μας θέλουμε να αλληλεπιδρά με αρχεία στον υπολογιστή (συνήθως .txt και .csv), υπάρχουν built-in συναρτήσεις.

Ασκήσεις

Ορισμένες ενδεικτικές ασκήσεις κατανόησης. Τα * στο τέλος αφορούν τον βαθμό δυσκολίας. Φυσικά αυτό είναι σε έναν βαθμό υποκειμενικό.

  1. Υλοποιείστε μία συνάρτηση που ελέγχει αν ένας αριθμός είναι πρώτος. Επιστρέφει TRUE αν είναι, FALSE αν δεν είναι. (*)

  2. Υλοποιείστε μία συνάρτηση που επιστρέφει το μεγαλύτερο στοιχείο μιας λίστας αριθμών που δέχεται ως παράμετρο (*)

  3. Υλοποιείστε μία συνάρτηση που δέχεται μία λίστα αριθμών ως παράμετρο και επιστρέφει την λίστα σε αύξουσα σειρά. Ποια μέθοδο χρησιμοποιήσατε; Τι θα μπορούσατε να βελτιώσετε; (**)

  4. Υλοποιείστε μία συνάρτηση που δέχεται ως παράμετρο μια σειρά λατινικών χαρακτήρων και την επιστρέφει σε κώδικα Μορς (*)

  5. Υλοποιείστε μία συνάρτηση που δέχεται ως παράμετρο μία λέξη και τη μετατρέπει στα “ποδανά(***)

  6. Υλοποιείστε μία συνάρτηση που δέχεται ως παράμετρο μία πρόταση και αντιστρέφει τις λέξεις από το τέλος προς την αρχή. (**)

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

  8. Υλοποιείστε μία συνάρτηση που δέχεται έναν αριθμό (ή έναν αριθμό ως σειρά χαρακτήρων) ως παράμετρο και προσθέτει τα ψηφία του μεταξύ τους, μέχρι να παραμείνει ένα. Μπορεί να υλοποιηθεί με αναδρομή; Ποια μέθοδος είναι βέλτιστη; (*)

Επικοινωνία/Συγγραφέας

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