Bevezetés az R programozási nyelvbe 3. Listák

Az R nyelvben a listák azt a célt szolgálják, hogy különböző típusú adatokat vagy adatstruktúrákat tároljunk bennük. Egy olyan doboznak képzelhetjük el ezeket, amely rekeszekre van osztva. S bár egy-egy rekeszbe csak azonos típusú dolgokat pakolhatunk, összességében nézve viszont a doboz tartalma igen változatos lehet. A listák éppúgy tartalmazhatnak egyedi számokat, karakterláncokat, logikai értékeket, mint vektorokat, adatkereket, sőt más listákat is.

A Bevezetés az R programozási nyelvbe sorozat írásai egymásra épülnek. Aki most ismerkedik az R-rel, annak erősen ajánlom, hogy a korábbi blogposztokkal kezdje. A sorozat eddig elkészült tagjai:
  1. Vektorok
  2. Adatkeretek
  3. Listák

Az alábbiakban R nyelven (v4.1.2) í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 lista létrehozása

A listák tehát bizonyos adatok vagy adatstruktúrák összefogására, együttes kezelésére valók. Szemben a vektorokkal, amelyekben azonos – csak numerikus, karakteres, vagy logikai – típusú egyedi adatokat tárolhatunk, a listák heterogének: a különböző típusokba tartozó egyedi adatok mellett adatstruktúrákat is tartalmazhatnak. Ezek adott esetben tetszőleges mélységben egymásba ágyazhatók.

A listákat a list() függvénnyel lehet létrehozni az R nyelvben. Ennek paraméterei a létrehozandó lista elemei lesznek.

Az alábbiakban egy olyan listát fogok előállítani, amelynek elemei a következők: egy karakterlánc, egy numerikus vektor, egy logikai érték, egy numerikus érték, egy adatkeret, valamint egy lista. Ezeket most közvetlenül a list() függvényben definiáltam, de természetesen annak sem lenne akadálya, ha paraméterként külső változókat adnánk meg.

# A lista létrehozása.
lista <- list("ez egy rövid szöveg",
              c(100,200,300,400,500),
              TRUE,
              4,
              data.frame("oszlop1" = 1:5, "oszlop2" = 6:10),
              list(TRUE,FALSE,FALSE,c(TRUE,TRUE,FALSE)))
# Az iménti lista tartalmának kinyomtatása a képernyőre.
print(lista)
## [[1]]
## [1] "ez egy rövid szöveg"
## 
## [[2]]
## [1] 100 200 300 400 500
## 
## [[3]]
## [1] TRUE
## 
## [[4]]
## [1] 4
## 
## [[5]]
##   oszlop1 oszlop2
## 1       1       6
## 2       2       7
## 3       3       8
## 4       4       9
## 5       5      10
## 
## [[6]]
## [[6]][[1]]
## [1] TRUE
## 
## [[6]][[2]]
## [1] FALSE
## 
## [[6]][[3]]
## [1] FALSE
## 
## [[6]][[4]]
## [1]  TRUE  TRUE FALSE

Hozzáférés a lista elemeihez

A listák tartalmára sokszor nem egyben, hanem elemenként van szükségünk. Ezek kiválasztásához tudni kell, hogy lista elemei fentről lefelé (balról jobbra) haladva, 1-től kezdve kapják meg a sorszámukat, vagyis az indexüket. (Vannak egyébként olyan programozási nyelvek is, amelyek 0-tól indexelnek.)

A lista elemeinek számát a length() függvénnyel kaphatjuk meg. A kiválasztandó elemek indexét a lista nevéhez illesztett dupla szögletes zárójelben kell megadni.

# A lista elemeinek száma.
length(lista)
## [1] 6
# A lista 1. elemének kiválasztása.
lista[[1]]
## [1] "ez egy rövid szöveg"

Ahogy korábban említettem, egy lista nemcsak egyedi értékeket, hanem adatstruktúrákat is tartalmazhat. Ez utóbbiak tovább indexelhetők a kérdéses adatstruktúra saját szabályai szerint. Ezeket a lehetőségeket korábban már igen részletesen bemutattam a vektorokról és az adatkeretekről szóló posztokban, míg a listák esetében ez e posztban leírtak az irányadók. Nézzünk most néhány példát ezekre!

# A lista 2. eleme egy vektor. 
# Ennek 4. és 5. elemét akarjuk elérni.
lista[[2]][4:5]
## [1] 400 500
# A lista 5. eleme egy adatkeret. 
# Ennek "oszlop1" nevű mezőjét akarjuk elérni.
lista[[5]]$oszlop1
## [1] 1 2 3 4 5
# Az iménti adatkeret 2. oszlopának 3. és 5. sorában lévő elemet akarjuk elérni.
lista[[5]][c(3,5),2]
## [1]  8 10
# A lista 6. eleme maga is egy lista.
# Ennek 4. eleme egy vektor.
# Ennek a vektornak a 2. elemét akarjuk elérni.
lista[[6]][[4]][2]
## [1] TRUE

Ha tehát valamilyen adatstruktúrát tartalmaz a listánk, akkor az adott elem kiválasztása után úgy kell folytatnunk az utasítást, mint ha egy önálló vektort, adatkeretet vagy éppen listát akarnánk indexelni.

A lista elemeinek elnevezése

Amennyiben nagyon sok eleme van a listánknak, akkor jobban járunk, ha ezeknek valamilyen nevet adunk, mert úgy könnyebb emlékezni rájuk.

A különböző objektumok – tehát nem csak a listák – elnevezésekor érdemes valamilyen egységes elvet követni. Én például általában az ún. camelCase kódolási konvenciót követem, amelyben az eredetileg több szóból álló elnevezések egyes elemei szóközök nélkül kerülnek egymás mellé, a második elemtől fogva mindig nagybetűvel kezdve. Egyébként pedig csupa kisbetűvel és ékezetek nélkül írom őket.

Bár az R programozási nyelv megengedné, hogy szóközöket használjunk az elnevezésekben, de ez bizonyos esetekben bonyodalmakhoz vezethet. Inkább kerüljük ezt a megoldást.

A camelCase kódolási konvenció

A camelCase kódolási konvenció (a kép forrása)

Az alábbi kódblokkban közvetlenül a lista létrehozásakor adom meg az egyes elemek nevét. Ezzel az új listával felülírom a régit.

# A lista létrehozása az elemek elnevezésével együtt.
lista <- list("egyediKarakterlanc" = "ez egy rövid szöveg",
              "vektor" = c(100,200,300,400,500),
              "egyediLogikai" = TRUE,
              "egyediNumerikus" = 4,
              "adatkeret" = data.frame("oszlop1" = 1:5, "oszlop2" = 6:10),
              "lista" = list(TRUE,FALSE,FALSE,c(TRUE,TRUE,FALSE)))
# Az iménti lista tartalmának kinyomtatása a képernyőre.
print(lista)
## $egyediKarakterlanc
## [1] "ez egy rövid szöveg"
## 
## $vektor
## [1] 100 200 300 400 500
## 
## $egyediLogikai
## [1] TRUE
## 
## $egyediNumerikus
## [1] 4
## 
## $adatkeret
##   oszlop1 oszlop2
## 1       1       6
## 2       2       7
## 3       3       8
## 4       4       9
## 5       5      10
## 
## $lista
## $lista[[1]]
## [1] TRUE
## 
## $lista[[2]]
## [1] FALSE
## 
## $lista[[3]]
## [1] FALSE
## 
## $lista[[4]]
## [1]  TRUE  TRUE FALSE

A lista elemeinek nevét a names() függvénnyel tudjuk lekérdezni. Ekkor egy karakteres vektort kapunk vissza.

names(lista)
## [1] "egyediKarakterlanc" "vektor"             "egyediLogikai"     
## [4] "egyediNumerikus"    "adatkeret"          "lista"

A neveket az unname() függvénnyel lehet eltávolítani a listából. Ha ezt az utasítást rámentjük az eredeti listánkra, akkor eltűnnek abból a nevek.

lista <- unname(lista)
names(lista)
## NULL

Arra is lehetőségünk van azonban, hogy utólag nevezzük el a lista egy-egy, vagy éppen az összes elemét. Ehhez ugyancsak a names() függvényt használjuk, amelynek paramétere maga a lista lesz. Ehhez rendeljük a hozzá karakteres formában a nevet vagy neveket.

  • Ha csak egy adott elemnek akarunk nevet adni, akkor annak indexét hozzá kell illeszteni az említett függvényhez.

  • Ha a lista minden elemének nevet akarunk adni, akkor értelemszerűen egy olyan karaktervektort kell megadnunk, amelynek hossza megegyezik a lista elemeinek számával. Egyébként csak a karaktervektor hosszának megfelelő számú elem kap nevet a listánk elején.

Amennyiben a lista adott eleme már el van nevezve, akkor az a művelet nyomán felülíródik.

names(lista)[[1]] <- "egyediKarakterlanc"
names(lista)
## [1] "egyediKarakterlanc" NA                   NA                  
## [4] NA                   NA                   NA
names(lista) <- c("egyediKarakterlanc","vektor","egyediLogikai","egyediNumerikus","adatkeret","lista")
names(lista)
## [1] "egyediKarakterlanc" "vektor"             "egyediLogikai"     
## [4] "egyediNumerikus"    "adatkeret"          "lista"

A lista elemeinek azért érdemes nevet adni, mert ettől kezdve az elnevezésekkel is hivatkozhatunk azokra. Ekkor a listaNeve$elemNeve formában tudunk hozzáférni a lista elemeihez. De természetesen a numerikus indexelés lehetősége is megmarad, sőt akár vegyesen is használhatjuk őket.

Lássuk ismét a korábban már megismert példákat, ezúttal az elnevezéseknek megfelelően átalakítva!

# A lista 2. eleme egy vektor és "vektor"-nak van elnevezve.
# Ennek 4. és 5. elemét akarjuk elérni.
lista$vektor[4:5]
## [1] 400 500
# A lista 5. eleme egy adatkeret és "adatkeret"-nek van elnevezve.
# Ennek "oszlop1" nevű mezőjét akarjuk elérni.
lista$adatkeret$oszlop1
## [1] 1 2 3 4 5
# Az iménti adatkeret 2. oszlopának 3. és 5. sorában lévő elemet akarjuk elérni.
lista$adatkeret[c(3,5),2]
## [1]  8 10
# A lista 6. eleme maga is egy lista és "lista"-nak van elnevezve.
# Ennek 4. eleme egy vektor.
# Ennek a vektornak a 2. elemét akarjuk elérni.
lista$lista[[4]][2]
## [1] TRUE

Ugyanakkor az elnevezések segítségével úgy is hivatkozni tudunk a lista elemeire, ha a dupla szögletes zárójelben karakterláncként adjuk meg azokat. Alább erre látunk egy példát.

# A lista "vektor" nevű elemének elérése.
lista[["vektor"]]
## [1] 100 200 300 400 500

A listák manipulálása

Az elemek felülírása

A listák egyes elemeit úgy tudjuk lecserélni, hogy az adott indexű vagy nevű elemet felülírjuk valami mással.

# A lista 1. elemének felülírása.
lista[[1]] <- "ez egy kicsit hosszabb szöveg"
# A lista 1. elemének kinyomtatása a képernyőre.
lista[[1]]
## [1] "ez egy kicsit hosszabb szöveg"
# A lista "vektor" nevű elemének felülírása.
lista$vektor <- 1:5
# A lista "vektor" nevű elemének kinyomtatása a képernyőre.
lista$vektor
## [1] 1 2 3 4 5

Az elemek törlése

Ha a NULL értékkel írunk felül egy elemet, akkor azzal törölni tudjuk a listából. Ebben az esetben az eltávolított elem indexét rögtön megkapja a sorban következő elem. Vagyis a lista indexei átszámozódnak. Erre figyelni kell, ha számokkal indexelünk!

# A lista 1. elemének törlése.
lista[[1]] <- NULL
# A lista 1. elemének kinyomtatása a képernyőre.
print(lista[[1]])
## [1] 1 2 3 4 5
# A lista aktuális hosszának megállapítása.
length(lista)
## [1] 5

Új elemek hozzáadása

Egy listához az append() függvénnyel tudunk új elemet hozzáadni. Ez alapértelmezés szerint lista végére kerül. Az after paraméter használatával viszont meghatározhatjuk azt az indexet, ami után be akarjuk szúrni az új elemet.

Most például visszahelyezem a lista elejére a törölt elemet, majd elnevezem azt az eredeti verziónak megfelelően.

# Új elem hozzáadása a lista 1. elemeként.
lista <- append(lista, "ez egy kicsit hosszabb szöveg", after = 0)
# A lista 1. elemének elnevezése.
names(lista)[[1]] <- "egyediKarakterlanc"
# A lista "egyediKarakterlanc" nevű elemének kinyomtatása a képernyőre.
lista$egyediKarakterlanc
## [1] "ez egy kicsit hosszabb szöveg"
# A lista aktuális hosszának megállapítása.
length(lista)
## [1] 6

Az imént egy egyedi értéket adtunk hozzá a listánkhoz. Ha viszont egy adatstruktúrát akarunk ezen a módon hozzáadni, akkor azt sajnos elemekre szedi és úgy csatolja a listához az append() függvény.

Próbáljunk meg például egy három elemből álló karaktervektort hozzáadni a listánk végéhez!

# Egy három betűt tartalmazó vektor hozzáadása a lista végéhez.
lista <- append(lista, c("A","B","C"))
# Az iménti lista tartalmának kinyomtatása a képernyőre.
lista
## $egyediKarakterlanc
## [1] "ez egy kicsit hosszabb szöveg"
## 
## $vektor
## [1] 1 2 3 4 5
## 
## $egyediLogikai
## [1] TRUE
## 
## $egyediNumerikus
## [1] 4
## 
## $adatkeret
##   oszlop1 oszlop2
## 1       1       6
## 2       2       7
## 3       3       8
## 4       4       9
## 5       5      10
## 
## $lista
## $lista[[1]]
## [1] TRUE
## 
## $lista[[2]]
## [1] FALSE
## 
## $lista[[3]]
## [1] FALSE
## 
## $lista[[4]]
## [1]  TRUE  TRUE FALSE
## 
## 
## [[7]]
## [1] "A"
## 
## [[8]]
## [1] "B"
## 
## [[9]]
## [1] "C"

Mit látunk itt? A megadott vektor elemei egyenként, önálló elemként adódtak hozzá a listához. És ehhez hasonló dolgot tapasztalnánk akkor is, ha egy adatkeret vagy egy másik lista hozzáadásával próbálkoznánk.

Ha tehát valamilyen adatstruktúrával szeretnénk kibővíteni a listánkat, akkor azt nem az append() függvénnyel tudjuk hozzáadni. Ebben az esetben úgy kell eljárni, hogy direktben a lista valamelyik eleméhez rendeljük hozza a kérdéses vektort, adatkeretet vagy listát.

Amennyiben a lista végéhez akarjuk hozzáadni új elemként az adatstruktúrát, akkor indexként a length(listaNeve)+1 értéket adjuk meg. Ezzel tudunk a lista aktuális hossza utáni első indexre pozícionálni. Ha jól dolgoztunk, akkor jelen esetben a 10. elemként kell megjelennie a hozzáadott vektornak.

# Egy három betűt tartalmazó vektor hozzáadása a lista végéhez.
lista[[length(lista)+1]] <- c("A","B","C")
# Az iménti lista tartalmának kinyomtatása a képernyőre.
lista[[10]]
## [1] "A" "B" "C"

Információk a listáról

A lista felépítéséről az str() függvénnyel kérhetünk rövid áttekintést. Ennek segítségével információkat kapunk a listánk egyes elemeiről: azok nevéről (ha van), típusáról és az első néhány adatról.

str(lista)
## List of 10
##  $ egyediKarakterlanc: chr "ez egy kicsit hosszabb szöveg"
##  $ vektor            : int [1:5] 1 2 3 4 5
##  $ egyediLogikai     : logi TRUE
##  $ egyediNumerikus   : num 4
##  $ adatkeret         :'data.frame':  5 obs. of  2 variables:
##   ..$ oszlop1: int [1:5] 1 2 3 4 5
##   ..$ oszlop2: int [1:5] 6 7 8 9 10
##  $ lista             :List of 4
##   ..$ : logi TRUE
##   ..$ : logi FALSE
##   ..$ : logi FALSE
##   ..$ : logi [1:3] TRUE TRUE FALSE
##  $                   : chr "A"
##  $                   : chr "B"
##  $                   : chr "C"
##  $                   : chr [1:3] "A" "B" "C"

A fentiekben egy bevezető jellegű áttekintést próbáltam adni az R programozási nyelvben használatos listákról. Ezek kezelése és manipulálása azonban nem merül ki a fentiekben. Jelen pillanatban viszont nem akarom túlbonyolítani a dolgot. Az esetspecifikus további lehetőségekre a későbbiekben még visszatérek majd.

comments powered by Disqus