Kategória: Adattudomány R-ben
|
Table of Contents
|
Bevezető
Áttekintés
Az R egy ingyenes, statisztikai célú programozási nyelv. A (fizetős) S rendszerre épül. Erejét az ingyenessége mellett a több ezer, ugyancsak ingyen elérhető csomag adja.
Telepítés
Az R alaprendszert erről az oldalról tudjuk letölteni: https://cran.r-project.org/bin/windows/base/. Feltelepítés után az asztalra kerülő ikon segítségével indíthatjuk el az RGui nevű programot, ami egy parancssori interfészt és néhány alap fejlesztői komponenst tartalmaz. Régebbi rendszereken telepítés után szükség lehet a bin könyvtárat beletenni a PATH környezeti változóba.
Integrált fejlesztőkörnyezetként az RStudio nevű szoftvert használhatjuk, amit innen tudunk letölteni: https://posit.co/download/rstudio-desktop/.
Parancssor
Indítsunk egy R terminált az alábbi módszerek egyikével:
- RGui
- RStudio
- az R parancs kiadása parancssorból
A megjelenő terminálban adjuk ki az alábbi parancsot:
print('Helló, R világ!')Az eredmény:
[1] "Helló, R világ!"Alapjellemzők
Az R nyelv néhány alapjellemzője az alábbi:
- Nincs szükség osztályt vagy függvényt létrehozni; a parancsok közvetlenül végrehajtódnak.
- Az utasításokat lezáró ; opcionális.
- Megjegyzés: # (több soros megjegyzés nincs)
- Blokk: {…}
- A változóknak nem kell megadni a típusát.
- Jelen vannak a programozási nyelvekre jellemző szokásos struktúrák.
- Jelentős számú globális függvény segíti a fejlesztést.
- Kiírni a print() függvénnyel tudunk, míg több részt összefűzni a paste() segítségével.
- Parancssorban print() nélkül is kiírja az eredményt.
Programfájl
Most megnézzük, hogy hogyan tudunk fájlban megírt R programot futtatni. Ez egyébként a gyakoribb módszer. A tartalom legyen az alábbi:
print('Helló, R programfájl világ!')A következőképpen tudjuk különböző módokon létrehozni és indítani:
RGui
- Létrehozás: File -> New script (Ctrl + N)
- Indítás: Edit -> Run all
RStudio
- Létrehozás: File -> New File -> R Script (Ctrl + Shift + N)
- Indítás: a forrás feletti sor jobb felső sarkában Source -> Source (Ctrl + Shift + S)
Parancssor
- Létrehozás: tetszőleges szövegszerkesztővel
- Indítás (feltéve, hogy a fájlnév az, hogy hello.R): R —slave —vanilla < hello.R
Nyelvi alapok
Lássunk egy példát!
# meghatározza egy egész számról, hogy prímszám-e
prím_e <- function(n) {
for (i in 2:sqrt(n)) {
if (n %% i == 0) {
return (FALSE)
}
}
return (TRUE)
}
for (i in c(15, 19, 25)) {
print(paste(i, prím_e(i)))
}Az eredmény:
[1] "15 FALSE"
[1] "19 TRUE"
[1] "25 FALSE"Az oka annak, hogy az eredményben ott van az [1] a sor elején az az, hogy az alaptípus vektor, a legegyszerűbb eredmény így nem skalár, hanem egyelemű vektor.
Segítség
Adott komponensről (függvény, csomag, konstans, adat) segítséget a ? segítségével kérhetünk. Pl.:
?pasteAz RStudio megjeleníti egy belső böngészőben. Parancssorból vagy RGui-ból kiadva a parancsot létrehoz egy webszervert, és megnyitja az oldalt az alapértelmezett böngésző segítségével.
Másik lehetőség:
help('paste')Ha csak részben emlékszünk a parancsra, akkor használhatjuk a dupla kérdőjelet:
??pastIll. az apropos() parancsot:
> apropos('past')
[1] "paste" "paste0"Az R számos beépített demót tartalmaz, amelyek a következő paranccsal listázhatóak:
demo()Adott terület demója:
demo('graphics')Könyvtár részletes leírásának megjelenítésére a vignette() parancsot használhatjuk. Vignetták listája:
vignette()Adott könyvtár vignettája:
vignette('colorspace')Változók
Áttekintés
A változók alapvető építőköveti minden programozási nyelvnek. Az R-ben az adja különös jelentőségét, hogy ott alapvetően adatokkal dolgozunk. Egy változónak van neve, típusa és értéke.
R-ben nem kell kiírni a változó típusát.
Értékadás
A többi programozási nyelvhez hasonlóan R-ben is működik az = jel:
a = 3Viszont eredendően az R-ben az értékadó operátor a <- volt:
b <- 2Jelenleg mindkét módszer használható, ám mint kódolási konvenció a <- megmaradt. A nyíl a másik irányba is mutathat: ->, pl.:
a + b -> cEgyszerre több változónak is értéket adhatunk, mindhárom operátor segítségével, pl. az alábbi módon:
3 -> a -> b -> cA rövidített értékadás (pl. a += 2 vagy a++) nincs R-ben.
Típusok
R-ben az alábbi típusokat használhatjuk:
- Atomi adattípusok:
- szöveg (character)
- valós szám (numeric)
- egész szám (integer)
- komplex (complex)
- logikai (logical)
- Összetett adattípusok:
- vektor (vector)
- mátrix (matrix)
- lista (list)
- faktor (factor)
- adat keret (data.frame)
Ezekről a későbbiekben még részletesen lesz szó.
Egy változó típusát a class() hívással tudjuk lekérdezni:
> a <- 3
> class(a)
[1] "numeric"Konverzió
Adattípusok között az as.*() hívásokkal tudunk konvertálni, pl.:
> s <- '5'
> class(s)
[1] "character"
> a <- as.numeric(s)
> class(a)
[1] "numeric"Atomi adattípusok
Szöveg
Típus: (character), ami kissé félrevezető; valójában nem karakterről, hanem szövegről, azaz stringről van szó.
A szöveg létrehozásakor idézőjelet és aposztrófot is használhatunk:
> 'Helló, világ!'
[1] "Helló, világ!"
> "Helló, világ!"
[1] "Helló, világ!"Szükség esetén a \ jelet használhatjuk prefixként (ún. escape karakterként), ha speciális karaktert szeretnénk írni, pl. idézőjelet és aposztrófot is.
Szöveg hossza: nchar(szöveg), pl.
> nchar('Csaba')
[1] 5Szöveg felbontása: strsplit(szöveg, elválasztó), pl.
> strsplit('alma, banán, narancs', ', ')
[[1]]
[1] "alma" "banán" "narancs"A más nyelvekhez szokott fejlesztőnek szokatlan lehet ez a szintaxis. Ahhoz talán már hozzászoktunk, hogy a kiíráskor a sor elején megjelenik egy [1]-es, itt viszont itt [[1]] szerepel. Vizsgáljuk meg, hogy mi a strsplit() eredménye!
> class(strsplit('alma, banán, narancs', ', '))
[1] "list"Tehát lista. A listákról később lesz szó; most elég annyit tudni, hogy az indexeléséhez dupla szögletes zárójelet kell használni:
> strsplit('alma, banán, narancs', ', ')[[1]]
[1] "alma" "banán" "narancs"Ez már többé-kevésbé az, amire számítottunk. Azért nézzük meg még ennek is a típusát:
> class(strsplit('alma, banán, narancs', ', ')[[1]])
[1] "character"Kissé furcsa, hogy a típus character, és nem mondjuk vektor. Ez minden bizonnyal amiatt van, mert az R-ben nincs olyan, hogy valami nem vektor.
Az indexelés R-ben 1-től kezdődik (erről részletesebben később), így adott gyümölcsöt a következőképpen érhetünk el:
> strsplit('alma, banán, narancs', ', ')[[1]][2]
[1] "banán"És valójában ez utóbbinak is a típusa character:
> class(strsplit('alma, banán, narancs', ', ')[[1]][2])
[1] "character"Szöveg összefűzése: paste() ill. paste0():
> paste('Helló, ', 'világ!')
[1] "Helló, világ!"
> paste('Helló, ', 'világ!', sep='')
[1] "Helló, világ!"
> paste0('Helló, ', 'világ!')
[1] "Helló, világ!"Tehát a paste() szóközökkel főzi össze a paraméterül átadott szövegdarabokat, a sep paraméterrel lehet ezt módosítani (aminek alapértelmezett értéke a szóköz), míg a paste0() esetén nincs elválasztó.
A szöveg része: a substr(szöveg, eleje, vége) vagy a substring(szöveg, eleje, vége) alkalmas szövegből részt kivágni:
> substr('szarvasmarha', 5, 7)
[1] "vas"Itt két lényeges eltérés van a többi programozási nyelvben szokásos és az R között:
- Az indexelés 1-től indul.
- A tól-ig határokat pontosan kell megadni, tehát a végéhez nem kell hozzáadni egyet.
Keresés és csere
A grep(mit, szöveg) és a grepl(mit, szöveg) függvények segítségével tudunk keresni egy szövegben. A grep() számot, míg a grepl() logikai értéket ad vissza:
> grep('vas', 'szarvasmarha')
[1] 1
> grepl('vas', 'szarvasmarha')
[1] TRUEReguláris kifejezésre 4 függvény is van: regexpr(kifejezés, szöveg), gregexpr(kifejezés, szöveg), regexec(kifejezés, szöveg) és gregexec(kifejezés, szöveg). Példa:
> regexpr('v.s', 'szarvas')
[1] 5
attr(,"match.length")
[1] 3
attr(,"index.type")
[1] "chars"
attr(,"useBytes")
[1] TRUEA sub(mit, mire, szöveg) az első előfordulást cseréli, míg a gsub(mit, mire, szöveg) az összeset:
> gsub('banán', 'mandarin', 'Kedvenc gyümölcsöm a banán. A banán finom.')
[1] "Kedvenc gyümölcsöm a mandarin. A mandarin finom."Átalakítások: a szöveg az R-ben (sok más nyelvhez hasonlóan) módosíthatatlan (immutable), tehát önmagában nem tudjuk módosítani. Az átalakítás tehát úgy történik, hogy az eredeti szöveg megmarad, és létrejön egy új. Példák:
> tolower('Csaba')
[1] "csaba"
> toupper('Csaba')
[1] "CSABA"Valós szám
A numeric adattípus a más programozási nyelvekben létező dupla pontosságú lebegőpontos számot jelenti, azaz tipikusan a double-nak nevezett adattípust, ami 8 bájtos számábrázolást jelent.
Alapból a számok numeric típusúak:
> a <- 3
> class(a)
[1] "numeric"A számokat exponenciális alakban is megadhatjuk:
> 1e3
[1] 1000
> 1e-3
[1] 0.001
> avogadro <- 6.02e23Műveletek
A szokásos alapműveleteket (összeadás, kivonás, szorzás, osztás) végre tudjuk hajtani:
> 3 + 2
[1] 5
> 5 - 2
[1] 3
> 3 * 2
[1] 6
> 6 / 3
[1] 2Tizedesvessző helyett az angolszász világban használatos tizedespontot kell alkalmaznunk:
> 3.2 + 2.4
[1] 5.6Az osztás valós:
> 9 / 4
[1] 2.25A nullával való osztás - ellentétben más programozási nyelvekkel - nem okoz hibát. Az eredmény lehet végtelen (Inf = infinity = végtelen), mínusz végtelen vagy nem definiált (NaN = not a number == nem egy szám).
> 1 / 0
[1] Inf
> -1 / 0
[1] -Inf
> 0 / 0
[1] NaNLétezik hatványozás is, kétféleképpen:
> 2 ^ 3
[1] 8
> 2 ** 3
[1] 8A hatványozás művelet segítségével pl. gyököt is tudunk vonni (a gyökvonás tehát az R-ben nyelvi elem):
> 2 ** 0.5
[1] 1.414214Kerekítés
Többféle kerekítési módszer létezik.
Szokásos matematikai kerekítés a round():
> round(4.6)
[1] 5A kerekítés eredménye is numeric típusú:
> class(round(4.6))
[1] "numeric"Ha a kerekítendő érték 0.5-re végződik, akkor páros irányba kerekít.
> round(3.5)
[1] 4
> round(4.5)
[1] 4
> round(5.5)
[1] 6A floor() a legnagyobb olyan egészre kerekít, ami kisebb vagy egyenlő mint a kerekítendő:
> floor(4.6)
[1] 4
> floor(-4.6)
[1] -5A ceiling() a legkisebb olyan egészre kerekít, ami nagyobb vagy egyenlő mint a kerekítendő:
> ceiling(4.6)
[1] 5
> ceiling(-4.6)
[1] -4A trunc() levágja a tizedes részt:
> trunc(4.6)
[1] 4
> trunc(-4.6)
[1] -4A signif() adott számú értékes jegyre kerekít:
> signif(3.141592, 3)
[1] 3.14További műveletek
- abs(x) - abszolút érték
- sqrt(x) - négyzetgyök
- sin(x), cos(x), tan(x), asin(x), … - trigonometrikus függvények
- log(x), log2(x), log10(x) - logaritmus
- exp(x) - exponenciális
Egész szám
Típus: integer. Az egész számok az R-ben előjeles 32 bitesek. Ez igen szűk, kb. -2 milliárd és 2 milliárd közötti értékek ábrázolását teszi lehetővé, ami egy kifejezetten statisztikai célú rendszertől meglehetősen szűk.
A számok alapból lebegőpontosak. Az L postfixet kell megadni ahhoz, hogy egészként ábrázolja:
> class(5)
[1] "numeric"
> class(5L)
[1] "integer"Meglepő, de még a hexadecimális formában megadott számok (a 0x prefixűek) is alapból lebegőpontosak; ott is az L postfixet kell megadni:
> class(0XFE)
[1] "numeric"
> class(0XFEL)
[1] "integer"Két egész típus között az összeadás, a kivonás és a szorzás eredménye is egész. Az osztás és a hatványozás viszont lebegőpontos eredményt ad, mgé akkor is, ha az eredmény amúgy egész lenne:
> class(3L + 2L)
[1] "integer"
> class(3L - 2L)
[1] "integer"
> class(3L * 2L)
[1] "integer"
> class(3L / 2L)
[1] "numeric"
> class(3L ** 2L)
[1] "numeric"
> class(3L ^ 2L)
[1] "numeric"Az osztásnál megjelenik a maradékos osztás. Arról már volt szó, hogy a sima / lebegőpontos eredményt ad. A maradékos osztás egész része a %/% művelet, míg a maradéka ez: %%:
> 11L / 4L
[1] 2.75
> 11L %/% 4L
[1] 2
> 11L %% 4L
[1] 3Ezek eredménye már egész:
> class(11L %/% 4L)
[1] "integer"
> class(11L %% 4L)
[1] "integer"Meglepő, de a maradékos osztás lebegőpontos számok esetén is működik:
> 5.9 %/% 2.4
[1] 2
> 5.9 %% 2.4
[1] 1.1Komplex
Típus: complex. A képzetes rész után kell írni az i postfixet. A szokásos működnek:
> (2 + 3i) * (4 - 1i)
[1] 11+10iÉrdekesség, hogy a -1 négyzetgyöke alapból NaN, viszont a -1+0i négyzetgyöke már i, egészen pontosan 0+1i
> (-1) ** 0.5
[1] NaN
> (-1+0i) ** 0.5
[1] 0+1iLogikai
Típus: logical. R-ben a két logikai érték: TRUE (igaz) és FALSE hamis. Logikai műveletek:
- &&: és
- ||: vagy
- !: tagadás
> !TRUE
[1] FALSE
> FALSE && TRUE
[1] FALSE
> FALSE || TRUE
[1] TRUEVektor elemenkénti logikai műveletek (a vektorokról később lesz szó):
- & elemenkénti logikai és
- & elemenkénti logikai vagy
- + elemenkénti logikai tagadás
Példák:
> c(FALSE, FALSE, TRUE, TRUE) & c(FALSE, TRUE, FALSE, TRUE)
[1] FALSE FALSE FALSE TRUE
> c(FALSE, FALSE, TRUE, TRUE) | c(FALSE, TRUE, FALSE, TRUE)
[1] FALSE TRUE TRUE TRUE
> !c(FALSE, TRUE)
[1] TRUE FALSEAz & és a | egyetlen elem esetén is működik:
> FALSE & TRUE
[1] FALSE
> FALSE | TRUE
[1] TRUESzövegből való konvertálással (ld. később) négyféleképpen tudunk logikai igaz és hamis értéket létrehozni:
> as.logical(c('TRUE', 'true', 'True', 'T', 'FALSE', 'false', 'False', 'F', 'something'))
[1] TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE NAÖsszetett adattípusok
Vektor (vector)
A legelemibb adattípus. A vektor ugyanolyan osztályú elemek listája. Létrehozási lehetőségek:
- Alapból mindegyik elem egy egyelemű vektor, pl. az x <- 5 hatására az x egy egyelemű vektor lesz.
- A : operátorral, pl. a 1:5 eredménye a következő 5 elemű vektor: 1 2 3 4 5.
- A c (concatenate, összefűzés) függvénnyel, pl. a v <- c(1, 5, 3:9) az 1 5 3 4 5 6 7 8 9 vektort hozza létre.
- Értékadás nélküli vektort a vector() függvénnyel hozhatunk létre, pl. a vector(length = 5, mode = "numeric") eredménye ez: [1] 0 0 0 0 0.
Az R-ben a legtöbb művelet vektorizált, ami azt jelenti, hogy mindegyik elemre végrehajtja. Pl. az 1:4 + 2*(5:8) eredménye ez lesz: [1] 11 14 17 20.
Halmaz műveletek vektoron:
- intersect(x, y) - metszet
- %in% - azt vizsgálja, hogy benne van-e egy elem a halmazban, pl. table(df$gyumolcs %in% c("alma", "barack"))
Mátrix (matrix)
A mátrix egy olyan speciális vektor, ami táblázatként van rendezve sorokba és oszlopokba. Technikailag, az R-ben a mátrix valójában egy vektor, dimenzió (dim) attribútummal. A dimenzió attribútum egy 2 elemű vektor. Mivel a mátrix is vektor, csak egyfajta elemet tartalmazhat.
Mátrixot a matrix() függvénnyel tudunk létrehozni, ahol megadhatjuk az adatokat oszloponként, majd a nrow és ncol paraméterekkel a mátrix dimenzióit adhatjuk meg. Példa: m <- matrix(1:12, nrow = 4, ncol = 3).
Utólag is mátrixot átméretezhetjük azzal, hogy a dim attribútumot átállítjuk. Oszlopot a cbind(), míg sor az rbind() függvénnyel adhatunk hozzá. Az egyes soroknak ill. oszlopoknak nevet is adhatunk a dimnames() függvény segítségével.
Lista (list)
Olyan vektor, amely különböző típusú adatokat tartalmazhat, pl l <- list(5, "alma", TRUE). Elem címzése: [[index]] (ha úgy címezzük mint a vektort ([index]), akkor egy egyelemű listát kapunk). Nevet is adhatunk az elemeknek, és akkor $ jellel hivatkozhatunk rá, pl. l <- list(szam=5, gyumolcs="alma", finom=TRUE), majd l$gyumolcs.
Faktor (factor)
Adatkategóriákat lehet vele létrehozni. Létrehozó függvény: factor(), pl. f <- factor(c("igen", "igen", "nem", "igen", "nem")).
Sorrend átrendezése: relevel(), pl. f2 <- relevel(f, "nem").
Adat keret (data.frame)
Adatok táblázatos elrendezésére szolgál (kb. mint egy adattábla az adatbázisban). A mátrixszal ellentétben különböző típusokat tartalmazhat. Reprezentáció: ezek valójában speciális listák, mégpedig a lista mindegyik eleme egy ugyanolyan hosszú vektor.
A lista egy eleme felfogható oszlopként. A listák hossza a sorok száma. Oszlopok nevei: row.names attribútumok.
Létrehozása: data.frame() függvénnyel, pl. df <- data.frame(szam=c(2, 3, 2, 3, 5), gyumolcs=c("alma", "banan", "alma", "barack", "banan")), valamint tipikusan beolvasásokkal (read.table()).
Feltételkezelés
Az if utasítás
Az if utasítás szintaxisa R-ben: if (feltétel) utasítás ill. if (feltétel) {utasítások}
Példa:
a <- 3
if (a > 2) {
print('Az a értéke nagyobb mint 2')
}Egyetlen utasítás esetén a kapcsos zárójel elhagyható:
a <- 3
if (a > 2)
print('Az a értéke nagyobb mint 2')Személy szerint én ki szoktam írni a kapcsos zárójelet még akkor is, ha egyetlen utasítás van a blokkban; ennek az az oka, hogy a zárójel elhagyása nehezen felderíthető hibákhoz vezethet.
Összehasonlítások
A fenti példában a "nagyobb mint" összehasonlításra látunk példát. A többi összehasonlító operátor (>: nagyobb, <: kisebb, >=: nagyobb vagy egyenlő, <=: kisebb vagy egyenlő, ==: egyenlő, !=: nem egyenlő) is működik:
> a <- 3
> a > 2
[1] TRUE
> a >= 2
[1] TRUE
> a < 2
[1] FALSE
> a <= 2
[1] FALSE
> a == 2
[1] FALSE
> a != 2
[1] TRUEAz else ág
Ha a feltétel nem igaz, akkor a vezérlés az else ágra fut:
a <- 1
if (a > 2) {
print('Az a értéke nagyobb mint 2')
} else {
print('Az a értéke nem nagyobb mint 2')
}Az összetett if…else
AZ else ágon az utasítás lehet egy újabb if. R-ben tehát nincs külön kulcssó erre, mint pl. más nyelvekben az elif.
a <- 2
if (a > 2) {
print('Az a értéke nagyobb mint 2')
} else if (a < 2) {
print('Az a értéke kisebb mint 2')
} else {
print('Az a értéke pont 2')
}A hármas operátor
A hármas operátor, tehát ami a ?: a legtöbb programozási nyelvben (feltétel ? egyik : másik), különleges módon van megoldva R-ben. Általánosságban: az utoljára kiszámolt érték az eredmény. Speciálisan ebben az esetben: ez az if és az else ágra is igaz, tehát ezeknek az ágaknak is van eredményük. Még speciálisabban, ez alkalmazható hármas operátorként, ebben a formában: if (feltétel) egyik else másik. Példa:
x <- -3
abszolút = if (x < 0) -x else x
print(abszolút)Ezzel a módszerrel, blokkokkal együtt, egész különleges dolgokat is megvalósíthatunk. Példa:
a <- 2
print(if (a > 2) {
'Az a értéke nagyobb mint 2'
} else if (a < 2) {
'Az a értéke kisebb mint 2'
} else {
'Az a értéke pont 2'
})Az egyes ágakban az eredmény természetesen lehet számított is; a lényeg az, hogy az eredmény kiszámolása legyen az utolsó művelet.
Értékadás feltételen belül
A feltételen belüli értékadás működik R-ben:
a <- 3
if ((b = 2 * a) > 4) {
print(paste0('Az b értéke (', b, ') nagyobb mint 4'))
}Ennek eredménye:
[1] "Az b értéke (6) nagyobb mint 4"A switch
Olyan szintaxissal nincs switch utasítás az R-ben, ahogy az számos programozási nyelvben létezik, ám a switch() utasítás lényegében ugyanazt a szerepet tölti be. Szintaxis:
switch(
érték,
ág1: 'eredmény1',
ág2: 'eredmény2',
ág3: 'eredmény3',
'alapértelmezett'
)Egy példa:
angol <- 'banana'
magyar <- switch(
gyümölcs,
apple = 'alma',
banana = 'banán',
orange = 'narancs',
'ismeretlen'
)
print(magyar)Az alábbiakat vegyük figyelembe:
- Az első paraméter az, ami alapján eldől, hogy melyik ágra fusson.
- A kulcsok idézőjel nélküli változók.
- Ez azt is jelenti, hogy nem kezdődhet számmal, nem lehet benne szóköz és számos más speciális karakter.
- Viszont az első paraméter string. A példában a 'banana' string az, ami alapján kiválasztjuk a megfelelő ágat, a megfelelő ág viszont az aposztrofok és idézőjelek nélküli banana.
- Az utolsó paraméter az alapértelmezett érték, azaz úgymond a default ág.
Ha több utasítást szeretnénk végrehajtani egy-egy ágon, akkor az értéket kapcsos zárójelbe is tehetjük.
angol <- 'orange'
magyar <- switch(
gyümölcs,
apple = {
eredmény <- 'alma'
print(eredmény)
eredmény
},
banana = {
eredmény <- 'banán'
print(eredmény)
eredmény
},
orange = {
eredmény <- 'narancs'
print(eredmény)
eredmény
},
'ismeretlen'
)
print(magyar)A fenti példákban az első paraméter szöveges. Lehet még pozitív egész szám is. Ez utóbbi esetben korlátozottabbak a lehetőségek:
- Nincsenek explicit kulcsok, hanem az értékeket sorban kell megadni az egyes, kettes, hármas stb. kulcshoz.
- Nem lehetséges alapértelmezett rtéket adni.
Példa:
x <- 3
print(switch(x, 2, 4, 8, 16))Ennek az eredménye a 8 lesz.
Összefoglalva: a switch() utasítás nagyrészt helyettesíti a más nyelvekben előforduló switch … case … default szerkezetet. Az egy jelentős megkötés, hogy a kiértékelt kifejezés csak legális változónevű szöveg vagy (igen korlátozottan) szám lehet, viszont az, hogy eredménye van a switch-nek, igen rugalmassá teszi a használatát.
Ciklusok
A for ciklus
Számos programozási nyelvben létezik külön számláló és végiglépkedő szintaxisa a for ciklusnak. Bizonyos esetekben az első az "igazi" for ciklus, míg a második a forEach. R-ben ugyanaz a szintaxisa mindkét típusnak. Egészen pontosan csak a második fajta létezik, de az első abból visszavezethető. Példa:
for (i in 1:5) {
print(i)
}Az eredmény:
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5Az 1:5 egy generált vektor, melyről később lesz még szó. Érdemes megfigyelni, hogy - más programozási nyelvektől eltérően - itt nem kell eggyel többet írni.
Egy vektor elemein a következőképpen tudunk végiglépkedni:
for (gyümölcs in c('alma', 'banán', 'narancs')) {
print(gyümölcs)
}Egy lista elemein hasonlóan (a listákról is lesz szó a későbbiekben):
for (gyümölcs in list('alma', 'banán', 'narancs')) {
print(gyümölcs)
}A while ciklus
A while az ún. elöl tesztelő ciklus: a ciklusmagot ismételten addig hajtja végre amíg a feltétel igaz. Szintaxisa: while (feltétel) utasítás ill. while (feltétel) {utasítások} . Példa:
i <- 1
while (i <= 5) {
print(i)
i <- i + 1
}Az eredmény:
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5A break
A break utasítás segítségével ki tudunk lépni az aktuális ciklusból (for, while, ill. a később bemutatott repeat), pl.
i <- 1
while (TRUE) {
print(i)
i <- i + 1
if (i > 5) {
break
}
}Az eredmény ugyanaz mint az előző példában.
A next
A next a többi programozási nyelv continue utasításának felel meg. Ennek segítségével a ciklusmag elejére ugrunk; for ciklus esetén tehát a következő értékre, míg while ciklus esetén a feltétel kiértékelésre. Példa:
for (i in 1:9) {
if (i %% 2 == 0) {
next
}
print(i)
}Ez a kód átlépi a páros számokat, így csak a páratlanokat írja ki. Az eredmény:
[1] 1
[1] 3
[1] 5
[1] 7
[1] 9A repeat
A repeat a végtelen ciklus; ekvivalens azzal, hogy while (TRUE). Csak a break utasítás segítségével tudunk belőle kilépni. Példa:
i <- 1
repeat {
print(i)
i <- i + 1
if (i > 5) {
break
}
}Az eredmény ugyanaz, mint a korábbi példáknál: kiírja a számokat 1-től 5-ig.
Függvények
Egyszerű függvény
Egyszerű, paraméter és visszatérési érték nélküli függvényt a következőképpen tudunk létrehozni:
kiír_helló <- function() {
print('Helló, világ!')
}Függvényhívás:
kiír_helló()Paraméterek
Példa paraméterrel rendelkező függvényre:
üdvözöl <- function(név) {
print(paste0('Szia ', név, '!'))
}
üdvözöl('Jóska')
üdvözöl('Pista')A paraméter típusát nem lehet megadni.
Visszatérési érték
A return() utasítással tudunk visszatérni, ahol az eredményt zárójelbe kell írni, tehát úgy, mintha függvényhívás lenne:
összead <- function(a, b) {
eredmény <- a + b
return(eredmény)
}
c <- összead(3, 2)
print(c) # 5A return() valójában opcionális; az utoljára kiszámolt érték az eredmény. A fenti példa így és más módokon módosított változata az alábbi:
összead <- function(a, b) {
a + b
}
print(összead(3, 2)) # 5Alapértelmezett paraméter
A paramétereknek lehet alapértelmezett értékük:
növel <- function(a, b=1) {
return(a + b)
}
print(növel(3, 2)) # 5
print(növel(3)) # 4Nevesített paraméterek
Tegyük fel, hogy van egy ilyen függvényünk:
kivon <- function(kisebbítendő, kivonandó) {
return(kisebbítendő - kivonandó)
}Hagyományos hívás:
print(kivon(5, 3))Nevesíthetjük is a paramétereket:
print(kivon(kisebbítendő=5, kivonandó=3))A sorrendet is felcserélhetjük:
print(kivon(kivonandó=3, kisebbítendő=5))Lokális és globális változók
Az R-ben egy változó hatóköre (scope) lehet globális és lokális. A globális változók nem tartoznak függvényhez, míg a lokálisak igen. Tekintsük az alábbi példát:
változó <- 2
f <- function() {
változó <- 3
print(változó)
}
print(változó)
f()
print(változó)AZ eredmény 2, 3, 2 lesz, azaz az történt, hogy létrehozott egy változó nevű változót globálisan és lokálisan is. Ha a globális változónak szeretnénk értéket adni azt a «- operátorral tudjuk megtenni:
változó <- 2
f <- function() {
változó <<- 3
print(változó)
}
print(változó)
f()
print(változó)Ebben az esetben az eredmény 2, 3, 3 lesz.
Ciklusfüggvények
A ciklusfüggvények nevében szerepel az, hogy apply. Arról van szó, hogy egy adathalmaz elemire végrehajt egy műveletet, paraméterül átadva neki egyesével az adathalmaz elemeit. Valójában tehát kb. ekvivalens azzal, mintha végig iterálnánk az input elemein, és egyeséve végrehajtanánk a műveletet, viszont sokkal tömörebb.
Jellegzetes eleme az R programozási nyelvnek, így szinte elkerülhetetlen, hogy belefussunk, emiatt itt is megtalálható. Alkalmazni viszont csak óvatosan érdemes, mert a kód ugyan tömörebbé válik, de a kezdők számára nehezebben olvasható lehet.
lapply()
A függvény első paramétere egy lista, melynek elemeit egyesével átadja a második paraméternek első elemként, ami egy függvény. Utána tetszőleges számú paramétert megadhatunk még, ami a második paraméterként megadott függvény további paraméterei lesznek. A bemenet és a kimenet is mindig lista (tehát pl. ha a bemenet 1:5, akkor abból olyan 5 elemű lista lesz, melynek eleme egyelemű vektorok).
Példa:
lapply(list(1:5, 3:9), mean)Itt először meghívja a mean(1:5), majd a mean(3:9) átlagot számoló függvényt, az eredmény pedig olyan kételemű lista lesz, melynek első eleme a 3-at tartalmazó egyelemű vektor, míg a második eleme a 6-ot tartalmazó egyelemű vektor:
[[1]]
[1] 3
[[2]]
[1] 6sapply()
Hasonló az előzőhöz, viszont az eredményt próbálja egyszerűsíteni, pl. sokkal könnyebben kezelhető vektorrá alakítani.
Példa:
sapply(list(1:5, 3:9), mean)Az eredménye a 3 és 6 elemekből álló kételemű vektor:
[1] 3 6apply()
A nevéből levonható következtetéssel ellentétben ez egy egészen speciális eset: egy mátrix sorain vagy oszlopain hajtja végre a műveletet. Az első paramétere tehát egy mátrix, a második azt jelzi, hogy soronként (1) vagy oszloponként (2) hajtsa-e végre a műveletet, majd jön a függvény és a további paraméterek.
Példa:
apply(matrix(1:6, 3, 2), 2, mean)Itt a mátrix 3 sort és 2 oszlopot tartalmaz, az átlag (mean) számítást oszloponként hajtja végre (2), és mivel az első oszlop elemei az 1, 2 és 3, a másodiké pedig a 4, 5 és 6, az eredmény a 2 és 5 számokból álló kételemű vektor lesz:
[1] 2 5mapply()
Egy bizonyos függvényt (első paraméter) hajt végre a második és harmadik paraméter kombinációiból.
Példa:
mapply(rep, 1:4, 4:1)A rep() függvény ismétlést jelent, pl. a rep(1, 4) eredménye 1, 1, 1, 1. Ez ekvivalens ezzel:
list(rep(1, 4), rep(2, 3), rep(3, 2), rep(4, 1))Eredmény:
[[1]]
[1] 1 1 1 1
[[2]]
[1] 2 2 2
[[3]]
[1] 3 3
[[4]]
[1] 4tapply()
Első paraméterként egy vektort vár, másodikként pedig faktort (vagy faktorként értelmezhető vektort), és a harmadik (és további) paraméterként megadott függvényt olyan bontásban hajtja végre, ahogyan azt a faktor kijelöli.
Példa:
tapply(1:20, c(rep("a", 5), rep("b", 10), rep("c", 5)), mean)Először három csoportra bontja az 1..20 közötti számokat, az első 5 lesz az 'a', a következő 10 a 'b', végül az utolsó 5 a 'c' kategória, és ezeken hajtja végre az átlagszámítást. Az eredmény ennek megfelelően a következő 3 elemű vektor lesz: 3.0, 10.5, 18.0, az elemek megfelelően elnevezve rendre a-bal, b-vel és c-vel:
a b c
3.0 10.5 18.0Ehhez kapcsolódó függvény a split(), amely a az első paraméterként átadott vektort bontja részekre a második paraméterként átadott faktor alapján. Példa:
split(1:20, c(rep("a", 5), rep("b", 10), rep("c", 5)))Eredmény:
$a
[1] 1 2 3 4 5
$b
[1] 6 7 8 9 10 11 12 13 14 15
$c
[1] 16 17 18 19 20Generikus függvények
Objektumorientáltság az R-ben
Az R-ben olyan értelembe vett klasszikus objektumorientáltság nincs, mint mondjuk a Java-ban, ám itt is bizonyos alapok meg vannak.
Az R-ben mindegyik objektumnak van osztálya, amelyet a class() függvénnyel tudunk megjeleníteni:
> class(5)
[1] "numeric"
> class("abc")
[1] "character"
> class(TRUE)
[1] "logical"Az osztály persze nemcsak ilyen "primitív" lehet, pl.:
> x <- rnorm(100)
> y <- x + rnorm(100)
> fit <- lm(y ~ x)
> class(fit)
[1] "lm"Az R-ben kétféle osztály létezik, melynek típusa S3 és S4. Ezek tehát az S nyelvből öröklődnek. Ezek párhuzamosan léteznek. Mi most az S4-et nézzük meg részletesebben, de egy ponton rövid kitérőt teszünk az S3-ra is.
Az R-ben sajátosan értelmezett az objektumorientáltság: létre tudunk hozni osztályokat a setClass() függvény segítségével, példányosíthatjuk is a new() függvény segítségével, ám az osztályokra jellemző függvényeket nem tudunk benne létrehozni. Helyette viszont létre tudunk hozni ún. generikus függvényeket, amelyek különböző osztályú inputra különböző osztályú outputot adnak eredményül.
Generikus metódusok
Példák a generikus metódusokra:
- mean(): ez S3 típusú
- print(): ez is S3 típusú
- plot(): ez S3 és S4 típusú is
- show(): ez S4 típusú
Az S3 típusú generikus függvény megvalósításait a methods() függvénnyel tudjuk lekérdezni, pl.:
methods("mean")Ez tehát azt írja ki, hogy mely S3 osztályok valósítják meg a mean() metódust.
Az S4 típusú osztályok metódusait a showMethods() függvénnyel tudjuk lekérdezni, pl.:
showMethods("mean")Ha megpróbáljuk egy S3 generikus függvény metódusait lekérdezni showMethods() hívással, vagy S4 generikus függvény metódusait methods() hívással, akkor hibát kapunk.
Lássunk egy plot() példát, ami jól illusztrálja a működést. (A diagramokról részletesen az R statisztika oldalon olvashatunk.) Az alapértelmezett plot() a szórásdiagram (angolul scatterplot):
set.seed(10)
x <- rnorm(100)
plot(x)
Viszont ha idősorozattá alakítjuk az inputot, akkor máris más lesz az eredmény:
set.seed(10)
x <- rnorm(100)
x <- as.ts(x)
plot(x)
Ha lekérdezzük a plot() generikus függvény S3 metódusait, akkor ott látjuk a plot.ts-t:
> methods("plot")
...
[45] plot.ts
...Az előző diagram tehát az alábbival megegyezik:
set.seed(10)
x <- rnorm(100)
plot.ts(x)De ez nem javasolt; a specifikus függvény helyett hívjuk meg megfelelő típussal a generikust.
Egy saját példa
Készítsünk el egy saját példát:
- Hozzunk létre egy polygon osztályt (setClass()).
- Valósítsuk meg rajta a plot metódust, ami kirajzol egy poligont (setMethod()).
- Példányosítsuk a polygon osztályt (new()).
- Hívjuk meg a plot() függvényt az imént létrehozott példányon.
library(methods)
setClass("polygon", representation(x = "numeric", y = "numeric"))
setMethod("plot", "polygon",
function(x, y, ...) {
plot(x@x, x@y, type = "n", ...)
xp <- c(x@x, x@x[1])
yp <- c(x@y, x@y[1])
lines(xp, yp)
}
)
p <- new("polygon", x = c(1, 3, 4), y = c(1, 3, 1))
plot(p)
Az R-ben az objektumorientált megközelítés eltér más nyelvektől, és a plot() függvény a példányhoz igazítja a működését. Ez a függvény két paramétert vár, x-et és y-t, itt csak x-et kap, ami viszont tartalmaz x-et és y-t is. A x@x jelenti az x-et, az x@y az y-t, az y pedig nincs használva. A kódban az xp és az yp a kapott két lista kiegészítve a végén az első elemekkel, hogy bezáruljon a poligon. A példában egy háromszöget rajzolunk ki.
R csomagok létrehozása
Áttekintés
Az alábbiakban elkészítünk egy R csomagot RSudio-ban, ami két függvényt tartalmaz: összeadást és szorzást. A leírást a https://ourcodingclub.github.io/tutorials/writing-r-package/ oldal alapján készítettem.
A lépések végrehajtásához szükség lesz két könyvtárra; ezeket előre telepítsük fel (remélhetőleg csak erre a kettőre; időközben többet is feltelepítettem, pl. desc, remotes):
install.packages("devtools")
install.packages("roxygen2")Csomag generálása
Első lépésben hozzunk létre egy megfelelő projektet:
- jobb felső sarokban kattintsunk a lefele mutató nyílra → New Project… → New Directory → R Package
- Package name: tetszőleges, pl. mymath
- Lent kiválaszthatunk egy könyvtárat, pl. ~/R/rpackage (az eredmény tehát a ~/R/rpackage/mymath/) könyvtárban lesz.
- Create Project
Létrejött két könyvtár és pár fájl. Lássuk a lényegeseket!
A csomag részei
DESCRIPTION
Ahogy nevéből kitalálható, ez adja a csomag leírását. Valójában adja magát, hogy hogyan kell kitölteni. Az egyetlen dolog, ami nincs benne a sablonban, de hozzáadtam, az a licensz.
Package: mymath
Type: Package
Title: Mathematical Calculations
Version: 1.0
Author: Csaba Farago
Maintainer: Csaba Farago <farago.csaba.phd@gmail.com>
Description: This package performs math operations.
Addition and multiplication is implemented.
License: GPL-3
Encoding: UTF-8NAMESPACE
Ide kerülnek az exportok, azaz pl. a csomag publikus függvényei. Egyelőre töröljük ki a fájlt; majd egy későbbi lépésben legeneráljuk.
R/ könyvtár
Ez a könyvtárszerkezet lényege, ugyanis ide kerülnek az R fájlok. Automatikusan létrejön egy hello.R, ami példát mutat. Töröljük ki. Hozzunk létre két fájlt az alábbi tartalommal:
R/myadd.R
#' Adding two numbers
#'
#' This function just adds two numbers. It returns the result.
#' Next line illustrating that this can be multiline.
#'
#' @param a the first number to be added
#' @param b the second number to be added
#' @return the sum of the two input values
#' @author Csaba Farago
#' @details
#' This function adds two numbers.
#' @export
myadd <- function(a, b) {
result = a + b
return(result)
}R/mymultiply.R
#' Multiplying two numbers
#'
#' This function just multiplies two numbers. It returns the result.
#'
#' @param a the first number to be multiplied
#' @param b the second number to be multiplied
#' @return the prod of the two input values
#' @author Csaba Farago
#' @details
#' This function multiplies two numbers.
#' @export
mymultiply <- function(a, b) {
result = a * b
return(result)
}Itt valósítjuk meg a függvényeket. Felette meghatározott formában kommentekbe írva adjuk meg azt a dokumentációt, ami szintén része lesz az eredménynek. Ez a Roxygen2 formátum. A példa mutat pár dolgot:
- Az első sor a cím. Ennek rövidnek kell lennie.
- Egy üres sor következik, majd egy hosszabb leírás.
- A @param a paramétereket sorolja fel. Szóközzel elválasztva kell megadni a paraméter nevét, majd újabb szóköz után a leírását.
- A @return a visszatérési értékről ad információt.
- A @author ennek a függvénynek a szerzője (ami eltérhet a teljes csomag szerzőjétől).
- A @details további technikai részleteket tartalmaz.
- A @export azt jelzi, hogy ez része a csomag publikus interfészének, azt exportáljuk. A későbbiekben belekerül a NAMESPACE fájlba.
man/ könyvtár
Oda kerül majd a generált dokumentáció sajátos formátumban. Az ott található hello.Rd fájlt töröljük ki.
Generálás
Töltsük be a szükséges könyvtárakat:
library("devtools")
library("roxygen2")Keressük meg a Build fület. Kicsit nehézkes megtalálni; előtte olyanok vannak, hogy Environment, History, Connections. Ott:
- More → Configure Build Tools…
- Build Tools → Generate documentation with Roxygen
- Configure… → legyen minden bepipálva
- Végül: Build → Install and Restart
Parancssorból a generálást az alábbi paranccsal hajthatjuk végre:
roxygenise()Vizsgáljuk meg az eredményt!
- DESCRIPTION: bekerült a Roxygen verziószáma RoxygenNote kulccsal.
- NAMESPACE: belekerültek az exportok: export(myadd) és külön sorban export(mymultiply), valamint felül egy figyelmeztetés, hogy ne szerkesszük kézzel. Valóban ne tegyük, mert akkor a Roxygen nem tudja generálni.
- man/ könyvtár: bekerült két .Rd kiterjesztésű fájl: myadd.Rd és mymutiply.Rd.
Használata
Most már ugyanúgy használhatjuk a csomagunkat, mint bármely másik csomagot. Betöltés:
library(mymath)Parancsok használata:
> myadd(3, 2)
[1] 5
> mymultiply(3, 2)
[1] 6Dokumentáció:
?myaddPublikálás
Számos helyre publikálhatunk, most a GitHub-ot nézzük meg. Ez kellően általános, de mégse "piszkítjuk" vele össze a hivatalos R csomag tárolót, a CRAN-t. A lépés végrehajtásához git és GitHub ismeretekre van szükség, melynek részletes ismertetése túlmutat a dokumentum keretein.
- Mindenekelőtt telepítsük fel a Gitet, regisztráljunk a GitHubon és hajtsuk végre a szükséges beállításokat, ahogyan az a Verziókövető rendszerek oldalon a megfelelő alfejezetekben le van írva.
- Hozzunk létre egy publikus repót a csomag nevével, pl. mymath.
- Abban a könyvtárban, ahol létrehoztuk a csomagot (az én esetemben ez C:/Users/Csaba/Documents/R/rpackage/mymath/) hozzunk létre egy git repository-t:
git init- Hozzunk létre tetszőleges szövegszerkesztővel egy .gitignore fájlt a fenti könyvtár gyökerében, az alábbi tartalommal (szükség esetén mással is kibővítve):
.Rproj.user/
.Rhistory
.RData- Adjuk hozzá a git repóhoz a fájlokat a szokásos módon:
git status
git add .
git commit -m "Initial commit."- Töltsük fel a GoitHubra a kódot (a saját azonosítónkkal):
git branch -M main
git remote add origin https://github.com/faragocsaba/mymath.git
git push -u origin mainKipróbálás
- Először töröljük a mymath csomagot:
remove.packages("mymath")- Indítsuk újra az RStudio-t.
- Töltsük be a devtools csomagot, majd adjuk ki az install_github() parancsot a megfelelő paraméterrel:
library(devtools)
install_github("faragocsaba/mymath")- Töltsük be a csomagot, és már használhatjuk is:
library(mymath)
?myadd
myadd(3, 2)Hibakeresés
Ha egy hiba okát szeretnénk megtalálni, olyan szintű hibakeresési lehetőségekre ne számítsunk, mint mondjuk a Java esetén rendelkezésünkre áll, de azért az R-ben is van pár lehetőség.
Stack trace
Egy hiba lokalizációjában sokat segít az ún. stack trace, azaz a hívási lánc. Parancssoros végrehajtás után adjuk ki a traceback() függvényt, és akkor megjelenít egy hevenyészett hívási láncot, nagy vonalakban megjelölve azt, hogy hol jött elő a hiba.
Debug
A program sorokat egyesével végrehajtani az egyik leghasznosabb hibakeresési módszer. Az R-ben kissé nehézkes, de működik. Ha a függvény neve, amit debuggolni szeretnénk, func, akkor adjuk ki ezt a parancsot: debug(func) (így, paraméterek nélkül), majd hívjuk meg a func() függvényt, megfelelően felparaméterezve. Ekkor nem fut le, hanem egyesével tudunk lépkedni a n beírásával. Ha már nem szeretnénk debug módban indítani, akkor hívjuk meg az undebug(func) függvényt.
Profiling
Nehezebb a dolgunk, ha a programunk funkcionálisan jól működik ugyan, viszont lassú. Ebben segítséget nyújthat az Rprof() függvény, ami mintavételezéssel készít statisztikákat. Alapértelmezésben nem csinál semmi hasznosít; ha pl. memória problémáink vannak, akkor érdemes így kiadni: Rprof(memory.profiling = TRUE). Az alapértelmezett mintavételezési gyakoriság 2 századmásodperc, így a fenti parancs kiadása után egy legalább ennyi ideig futó programot indítsunk. Ha befejeződött, akkor adjuk ki a summaryRprof() parancsot.






