Bevezetés az R programozási nyelvbe 1. Vektorok

Ebben a most induló sorozatban igyekszem szisztematikusan feltérképezni az R nyelv fontosabb alkotóelemeit. Első alkalommal az úgynevezett vektorokról lesz szó. Ezt az lépten-nyomon használt adatstruktúrát úgy kell elképzelni, mint a halmazokat. Az adatok egy halmazát.

Az alábbiakban R nyelven (v4.0.3) írt kódot használok a feladat végrehajtásához. A magyarázó szövegek közé ékelt fekete kódblokkok tartalmát az RStudio-ban egymás alá illesztve elvileg bárki által reprodukálható az itt bemutatott műveletsor. A kódblokkok # kezdetű sorai pusztán magyarázó funkcióval bírnak, ezekre a program futtatásakor nincs szükség.

A vektor típusa és létrehozása

A vektor (vector) egy olyan adatstruktúra, amely azonos típusú értékek tárolására szolgál. Ennek megfelelően a vektorok legtöbbször (de nem kizárólagosan) a következő típusokba sorolhatók:

  • integer

  • double

  • character

  • logical

Az integer és a double típusokat összefoglalóan numerikus (numeric) vektoroknak szokták nevezni. Az előbbiben egész számokat, az utóbbiban bármilyen számot tárolhatunk. A karakteres vektorokban vagy önálló karaktereket, vagy karakterláncokat rögzíthetünk. Végül a logikai vektorban igaz (TRUE, T) és hamis (FALSE, F) értékeket tudunk tárolni. Az itt felsorolt típusú adatok tehát ugyanazon vektoron belül nem keveredhetnek.

Nézzünk néhány példát ezekre! A különálló értékeket a c() (combine) függvénnyel tudjuk vektorrá összefűzni. A sorok elején megnevezett változókban el is tárolhatjuk ezeket egy értékadó operátor (<-) segítségével. A későbbiekben a változó tartalmára annak nevével tudunk majd hivatkozni. Például a typeof() függvényben, amely visszaadja annak típusát.

Az R érzékeny a kis- és a nagybetűk közötti különbségre. Erre a változók elnevezésekor, illetve az azokra való hivatkozáskor figyelemmel kell lenni.

vektInteger <- c(1L, 2L, 3L)
typeof(vektInteger)
## [1] "integer"
vektDouble <- c(1, 1.2, 1552)
typeof(vektDouble)
## [1] "double"
vektCharacter <- c("R", "vektor", "1")
typeof(vektCharacter)
## [1] "character"
vektLogical <- c(TRUE, FALSE)
typeof(vektLogical)
## [1] "logical"

Néhány megjegyzés a fentiekhez:

  • Ha feltétlenül integer típusú vektort akarunk létrehozni, akkor az egész számok mögé egy L betűt kell írni. Egyébként, ha simán csak egész számokat használunk, a vektor típusa double lesz.

  • A magyar gyakorlatban elterjedt tizedesvessző helyett az R-ben a tizedespont használandó!

  • Egy vektor akkor lesz character típusú, ha az értékei idézőjelben szerepelnek. Ezeket így kell megadni és a konzolon is így jelennek meg. Ahogy látható, a karaktervektorban számok is tárolhatók. De ezeket, mivel idézőjelben vannak, szövegnek tekinti az R.

A vektorok típusára nemcsak általában, hanem konkrétan is rá tudunk kérdezni az is.integer(), is.double(), is.numeric(), is.caharcter(), is.logical() fügyvényekkel. Ha eltaláltuk, akkor TRUE, ha tévedtünk, akkor FALSE eredményt kapunk.

Típuskonverzió

Egy vektor típusa azért lehet érdekes a számunkra, mert ez határozza meg, hogy milyen műveleteket tudunk végrehajtani rajta. Az R egy úgynevezett gyengén típusos nyelv, ami lehetőséget kínál a vektorok típusának átkonvertálására, illetve az automatikus típuskonverzióra. Direktben az as.integer(), as.double(), as.numeric(), as.character(), as.logical() függvényekkel tudjuk a megadott típusra kényszeríteni a vektort. Természetesen csak ott, ahol nincs elvi akadálya az átváltásnak. A teljesség igénye nélkül nézzünk a konverzióra néhány példát!

# Character > double típusra.
vekt <- as.double(c("1", "2", "3"))
typeof(vekt)
vekt
## [1] "double"
## [1] 1 2 3

# Double > character típusra.
vekt <- as.character(c(1, 2, 3))
typeof(vekt)
vekt
## [1] "character"
## [1] "1" "2" "3"

# Double > logical típusra.
vekt <- as.logical(c(0, 1.2, 2))
typeof(vekt)
vekt
## [1] "logical"
## [1] FALSE TRUE TRUE

# Logical > integer típusra.
vekt <- as.integer(c(TRUE, TRUE, FALSE))
typeof(vekt)
vekt
## [1] "integer"
## [1] 1 1 0

# Double > integer típusra.
vekt <- as.integer(c(3.2, 4.5, 6.3))
typeof(vekt)
vekt
## [1] "integer"
## [1] 3 4 6

Ahogy látható, akár karakteres, akár numerikus formában vannak megadva, a számok minden további nélkül átváltódnak egymásra. A logikai értékek számmá változtatása esetén a FALSE mindig 0 lesz, a TRUE pedig 1, illetve fordított esetben a 0 mindig FALSE, a többi szám pedig TRUE. Ha double típusú adatokat akarunk átváltani integerre, akkor csak a számok egész részét kapjuk vissza. Nyilvánvaló ugyanakkor, hogy nem mindig lehetséges a vektor (minden elemének) átváltása.

# Character > double típusra.
vekt <- as.double(c("egy", "kettő", "3"))
typeof(vekt)
vekt
## Warning: NAs introduced by coercion
## [1] "double"
## [1] NA NA 3

A fenti példában, ahol egy character típusú vektort kíséreltem meg double típusra konvertálni, a betűvel kiírt számokat természetesen nem tudta átváltoztatni az R. Ezek helyére az NA került be az eredményül kapott vektorba. Az NA egy speciális érték. Az angol Not Available rövidítése, magyar megfelelője a véletlenül szintén NA-val rövidíthető Nincs Adat.

Ha egy vektor tartalmát le akarjuk ellenőrizni ebből a szempontból, akkor az is.na() függvénnyel tehetjük meg. Ez egyenként megvizsgálja a vektor elemeit. Ahol NA értéket talál, ott TRUE, egyébként FALSE értéket ad vissza. Vagyis egy logikai értékekből álló vektort kapunk eredményül.

A logikai (vagy azzá konvertálható) vektoroknál az any() függvény TRUE kimenetet produkál, ha a vektor elemei közül legalább egynek TRUE az értéke. Az all() függvény viszont csak akkor ad TRUE eredményt, ha a vektor minden eleme TRUE.

is.na(vekt)
## [1]  TRUE  TRUE FALSE
any(is.na(vekt))
## [1] TRUE
all(is.na(vekt))
## [1] FALSE

A vektorokon végzett műveletek eredményére befolyással lehetnek az azokban esetlegesen előforduló NA értékek. Erre érdemes mindig figyelni!

A vektorok létrehozásának egyéb lehetőségei

A vektorok elemeit nemcsak felsorolással, hanem egy sorozat tagjaként is megadhatjuk. A seq(from, to, by, length.out) függvénnyel olyan számsorozatot tudunk generálni, amely a from paraméterben megadott számmal kezdődik és a by paraméter értékével növekszik vagy csökken egészen addig, ameddig meg nem haladja – azaz nem feltétlenül éri el! – a to paraméter értékét. A by paraméter alapértelmezett értéke 1. Ha így akarjuk használni, akkor elhagyható. (Ebben az esetben legenerálódik a sorozat úgy is, ha simán a from:to alakban definiáljuk azt, például: 1:10.) A by értéke negatív is lehet. Ezzel csökkenő sorozatot tudunk generálni, figyelve természetesen arra is, hogy a from értéke nagyobb legyen a to paraméternél. Amennyiben a by helyett a length.out paramétert használjuk, akkor egy olyan számsorozat keletkezik, amelynek két végpontjában a from és a to értéke áll, és a vektor a length.out paraméterben megadott számú elemből áll.

seq(0.1, 3)
## [1] 0.1 1.1 2.1
1:10
##  [1]  1  2  3  4  5  6  7  8  9 10
seq(1, 20, by = 3)
## [1]  1  4  7 10 13 16 19
seq(20, -10, by = -4)
## [1] 20 16 12  8  4  0 -4 -8
seq(0.1, 9, length.out = 6)
## [1] 0.10 1.88 3.66 5.44 7.22 9.00

Az angol ábécé nagybetűit tartalmazó vektort a LETTERS, a kisbetűit tartalmazót pedig a letters utasítással generálhatunk.

LETTERS
##  [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S"
## [20] "T" "U" "V" "W" "X" "Y" "Z"
letters
##  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s"
## [20] "t" "u" "v" "w" "x" "y" "z"

Egy vektort előállíthatunk úgy is, ha egy létező vektor elemeit bizonyos szabályok szerint ismételgetjük. A rep(x, times, each, length.out) függvény numerikus és karakteres vektorokat egyaránt elfogad bemenetként, ami mindig fixen az első paraméter. Ha a times paraméterként egyetlen számot adunk meg, akkor a teljes vektor tartalma ennyi alkalommal fog megismétlődni. Olyan eredményt kapunk, mintha többször egymás után lemásolnánk azt. Lehetőség van azonban arra is, hogy ne egyben az egész vektort, hanem annak egyes elemeit ismételgessük. Ekkor a times paraméternek egy olyan numerikus vektort kell megadni, amely elemeinek száma megegyezik a bemeneti vektoréval. Az utóbbi 1. eleme a times 1. elemeként megadott számnak megfelelő alkalommal fog ismétlődni és így tovább a többi elem is. Amennyiben a bemeneti vektor minden egyes elemét azonos számú alkalommal akarjuk megismételni, akkor ehhez használjuk az each paramétert. Végül a length.out paraméterrel azt tudjuk beállítani, hogy az ismétléssel előállított vektorból hány elemet kérünk vissza annak elejétől számolva. Ha mindet, akkor ez elhagyható.

vekt <- c("egy", "kettő", "három")
rep(vekt, times = 3)
## [1] "egy"   "kettő" "három" "egy"   "kettő" "három" "egy"   "kettő" "három"
rep(vekt, times = c(1:3))
## [1] "egy"   "kettő" "kettő" "három" "három" "három"
rep(vekt, each = 3)
## [1] "egy"   "egy"   "egy"   "kettő" "kettő" "kettő" "három" "három" "három"
rep(vekt, each = 3, length.out = 7)
## [1] "egy"   "egy"   "egy"   "kettő" "kettő" "kettő" "három"

A fentiek mellett egy vektort úgy is létre tudunk hozni, ha több vektort egyesítünk a c() függvénnyel. Az egyesítendő vektoroknak nem feltétlenül kell azonos típusú adatokat tartalmazniuk, mivel az automatikus típuskonverzió ebben az esetben is érvényesül.

vektA <- c(1:3)
vektB <- c("1","2","3")
c(vektA, vektB)
## [1] "1" "2" "3" "1" "2" "3"
c(vektA, TRUE, TRUE, FALSE)
## [1] 1 2 3 1 1 0
c(vektB, TRUE, TRUE, FALSE)
## [1] "1"     "2"     "3"     "TRUE"  "TRUE"  "FALSE"
c(rep("hurrá", times = 3), c("egy", NA, "három"))
## [1] "hurrá" "hurrá" "hurrá" "egy"   NA      "három"

A sample(x, n) függvény az első paraméterként megadott vektorból véletlenszerűen kiválaszt n darab elemet. Ezeket a sort(x, decreasing) függvénybe csomagolva tudjuk nagyság szerinti sorba rendezni. A decreasing paraméter alapértelmezett értéke FALSE. Ezt csak akkor kell megadnunk és TRUE-ra változtatnunk, ha csökkenő sorrendet akarunk elérni.

sort(sample(1:90, 5))
## [1] 69 74 75 76 87
sort(sample(LETTERS, 10), decreasing = TRUE)
##  [1] "Y" "X" "W" "U" "Q" "P" "J" "H" "D" "B"

Hozzáférés a vektor elemeihez

Sokszor előfordul, hogy nem a vektor egészére, hanem annak csak bizonyos elemeire van szükségünk. Ezek kiválasztására is lehetőségünk van természetesen. Ehhez elöljáróban tudni kell, hogy a vektor elemei balról haladva, 1-től kezdve kapják meg a sorszámukat vagy másképpen indexüket. (Egyes programozási nyelvek 0-tól indexelnek.)

A kiválasztandó elemek indexét a vektorhoz illesztett szögletes zárójelben lehet megadni. Ide vagy egyetlen számot kell írnunk, vagy egy másik vektor megadásával tudjuk kijelölni a szükséges elemeket. Ha logikai értékeket (TRUE, FALSE) használunk az indexeléshez, akkor a szögletes zárójelben az eredeti vektorunk minden elemére vonatkozóan nyilatkoznunk kell, egyébként hibaüzenetet kapunk.

vekt <- LETTERS
vekt[1]
## [1] "A"
vekt[1:10]
##  [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J"
vekt[seq(1, 26, by = 2)]
##  [1] "A" "C" "E" "G" "I" "K" "M" "O" "Q" "S" "U" "W" "Y"
vekt[c(1,4,2,3,5)]
## [1] "A" "D" "B" "C" "E"
vekt[c(rep(TRUE, times = 10), rep(FALSE, times = 16))]
##  [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J"
vekt[1:5][c(TRUE, FALSE, FALSE, TRUE, TRUE)]
## [1] "A" "D" "E"

Elképzelhető persze, hogy nem tudjuk konkrétan megmondani a kívánt elemek sorszámát, de valamilyen feltétel alapján ki tudjuk jelölni azokat. Ekkor a szögletes zárójelben egy logikai kifejezést kell megadni.

A logikai kifejezések a kiértékelésük után TRUE vagy FALSE eredményt adnak. Egyszerre több logikai kifejezést is használhatunk. Ezeket úgynevezett logikai operátorokkal kell összekapcsolni.

Jelen esetben a bemeneti vektorunknak azokat az elemeit fogjuk visszakapni, amelyekre nézve igaznak bizonyul a megadott feltétel. (Ugyanakkor egyáltalán nem biztos, hogy bármit is visszakapunk a vektorból!) Amennyiben nem a vektorban tárolt értékekre, hanem az elemek indexére van szükségünk, akkor a logikai kifejezést a which() függvénybe kell ágyazni.

vekt <- sort(sample(1:100, 20))
vekt
##  [1] 11 14 17 26 35 40 41 44 51 55 58 65 67 68 70 78 79 85 87 94
vekt[vekt <= 20]
## [1] 11 14 17
vekt[vekt < 20 & vekt > 10]
## [1] 11 14 17
vekt[vekt < 20 | vekt > 10]
##  [1] 11 14 17 26 35 40 41 44 51 55 58 65 67 68 70 78 79 85 87 94
vekt[!(vekt < 20)]
##  [1] 26 35 40 41 44 51 55 58 65 67 68 70 78 79 85 87 94
vekt[vekt > 100]
## integer(0)
vekt <- LETTERS
which(vekt %in% c("A","B","C"))
## [1] 1 2 3
vekt[which(vekt %in% c("A","B","C"))]
## [1] "A" "B" "C"

A head(x, n) függvénnyel a bemeneti vektor első n elemét, a tail(x, n) függvénnyel pedig az utolsó n elemét kapjuk meg. Alapértelmezés szerint az n paraméter értéke 6. Ha nem adjuk meg, akkor ennyi elemet kapunk vissza.

vekt <- letters
head(vekt)
## [1] "a" "b" "c" "d" "e" "f"
which(vekt %in% head(vekt))
## [1] 1 2 3 4 5 6
tail(vekt, 10)
##  [1] "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"

A vektorokban tárolt értékeknek nevet is adhatunk, hogy később azzal hivatkozhassunk rájuk. Erre több alternatívát mutatok alább, amelyekben a names(), a setNames() és a c() függvényt fogom használni. (Az R nyelvre általában véve is jellemző, hogy ugyanazt a feladatot többféle megközelítésben is végrehajthatjuk.) A neveket az unname() függvénnyel tudjuk eltávolítani a vektorból.

vekt <- 1:5
names(vekt) <- c("egy", "kettő", "három", "négy", "öt")
vekt
##   egy kettő három  négy    öt 
##     1     2     3     4     5
vekt <- setNames(1:5, c("egy", "kettő", "három", "négy", "öt"))
vekt
##   egy kettő három  négy    öt 
##     1     2     3     4     5
vekt <- c("egy" = 1, "kettő" = 2, "három" = 3, "négy" = 4, "öt" = 5)
vekt
##   egy kettő három  négy    öt 
##     1     2     3     4     5
names(vekt)
## [1] "egy"   "kettő" "három" "négy"  "öt"
vekt[c("egy","öt")]
## egy  öt 
##   1   5
unname(vekt)
## [1] 1 2 3 4 5

Vektorok tartalmának módosítása és törlése

A létrehozott vektorok tartalma módosítható. Például egyszerűen felülírhatjuk az egészet valami mással. Lehetséges azonban az is, hogy a fentiekben elmondottak szerint kijelöljük a vektor bizonyos elemeit, és csak azoknak adunk új értéket.

vekt <- c("A","B","C","D","E")
vekt <- c("F",NA,"H",NA,"J")
vekt
## [1] "F" NA  "H" NA  "J"
vekt[2] <- "G"
vekt[is.na(vekt)] <- "I"
vekt
## [1] "F" "G" "H" "I" "J"
vekt[1] <- vekt[5]
vekt[2:5] <- c("K","L","M","N")
vekt
## [1] "J" "K" "L" "M" "N"

Ha a vektor teljes tartalmát törölni szeretnénk, akkor a NULL értéket kell hozzárendelni, ami az üresnek megfelelő jelentéssel bír. Vagyis a NULL nem egyenlő a nullával!

vekt <- 1:5
vekt <- NULL
vekt
## NULL
is.null(vekt)
## [1] TRUE
0 %in% vekt
## [1] FALSE

Aritmetikai műveletek a vektorokkal

A numerikus vektorok elemein aritmetikai műveleteket is végrehajthatunk. Amennyiben az operandusok mindegyike vektor, akkor az azonos indexű elemek között megy végbe a művelet. Ha az egyik vektornak valami miatt kevesebb eleme lenne, akkor a rendelkezésre álló elemeket ismételgetve feltöltődnek a hiányzó helyek és maga a művelet egy figyelmeztetés kíséretében hajtódik végre. (Amit egy hosszabb algoritmus lefuttatása közben nem feltétlenül veszünk észre, szóval erre vigyázni kell.) Ha az egyik operandust egyetlen szám alkotja, akkor művelet a másik operandusként megadott vektor minden elemén végrehajtódik.

vektA <- c(1,2,3,4,5)
vektB <- c(6,7,8,9,10)
vektA + vektB
## [1]  7  9 11 13 15
vektA - vektB
## [1] -5 -5 -5 -5 -5
vektA * vektB
## [1]  6 14 24 36 50
vektA / vektB
## [1] 0.1666667 0.2857143 0.3750000 0.4444444 0.5000000
vektA + c(1,2)
## Warning in vektA + c(1, 2): longer object length is not a multiple of shorter
## object length
## [1] 2 4 4 6 6
vektA * 5.5
## [1]  5.5 11.0 16.5 22.0 27.5

Az R base nevű alapcsomagjában olyan függvények is találhatók, amelyek megkönnyítik bizonyos matematikai műveletek végrehajtását. A teljesség igénye nélkül néhányat megemlítve: a sum() összeadja a paraméterként megadott vektor elemeit, a cumsum() kumulált összegekből álló vektort hoz létre a bemeneti vektorból, és ugyanezeket csinálja a szorzással a prod() és a cumprod() függvény. Hasznos lehet még a min(), a max() és az abs() függvény is, amelyek neve magáért beszél.

vekt <- c(1,2,3,4,5)
sum(vekt)
## [1] 15
cumsum(vekt)
## [1]  1  3  6 10 15
prod(vekt)
## [1] 120
cumprod(vekt)
## [1]   1   2   6  24 120
min(vekt)
## [1] 1
max(vekt)
## [1] 5
abs(-1)
## [1] 1

Halmazműveletek a vektorokkal

A vektorokat felfoghatjuk úgy is, mintha halmazok lennének. Ez lehetőséget ad arra, hogy halmazműveleteket végezzünk rajtuk. A vektor elemeinek számát – a halmaz számosságát – a length() függvénnyel kaphatjuk meg.

length(LETTERS)
## [1] 26

Két vektor különbségét a setdiff(A, B) függvénnyel kaphatjuk meg. Ennek eredménye egy olyan vektor lesz, amelynek elemei A-ban benne vannak, de B-ben nincsenek. (Mivel a különbségképzés nem kommutatív, ezért a bemeneti vektorok felcserélésével más eredményt kapunk.) Az intersect(A, B) függvény a két vektor metszetét, vagyis közös részét adja vissza. A vektorok uniójának megvalósítása során pedig jól jöhet a unique() függvény, amely eltávolítja a korábban megtanult módon egyesített vektorokból az ismétlődő elemeket.

vektA <- c("A","B","C","D","E")
vektB <- c("D","E","F","G","H")
setdiff(vektA, vektB)
## [1] "A" "B" "C"
setdiff(vektB, vektA)
## [1] "F" "G" "H"
intersect(vektB, vektA)
## [1] "D" "E"
unique(c(vektA, vektB))
## [1] "A" "B" "C" "D" "E" "F" "G" "H"

A fentieken túl kismillió olyan függvény létezik az R nyelvben, amely vektorokat vár bemenetként, vagy éppen vektorokat állít elő. E blogposztban azokat a próbáltam kiemelni, amelyekre gyakran szükségünk lehet és ezért mindenképpen érdemes ismerni őket.

comments powered by Disqus