3 Adatkezelés R-ben
3.1 Az adatok importálása
Az adatok importálására az R alapfüggvénye mellett több csomag is megoldást kínál. Ezek közül a könyv írásakor a legnépszerűbbek a readr
és a rio
csomagok. A szövegek különböző karakterkódolásának problémáját tapasztalataink szerint a legjobban a readr
csomag read_csv()
függvénye kezeli, ezért legtöbbször ezt fogjuk használni a .csv
állományok beolvasására. Amennyiben kihasználjuk az RStudio projekt opcióját (lásd a Függelékben) akkor elegendő csak az elérni kívánt adatok relatív elérési útját megadni (relative path). Ideális esetben az adataink egy csv fájlban vannak, ahol az egyes értékeket vesszők (vagy egyéb speciális karakterek) választják el. Ez esetben a read_delim()
függvényt is használhatjuk. A beolvasásnál egyből el is tároljuk az adatokat egy objektumban. A sep =
opcióval tudjuk a szeparátor karaktert beállítani, mert előfordulhat, hogy vessző helyett pontosvessző tagolja az adatainkat.
Az R képes linkről letölteni fájlokat, elég megadnunk egy működő elérési útvonalat (lenti kódrészlet nem egy valódi linkre mutat, csupán egy példa).
Az R csomag ökoszisztémája kellően változatos ahhoz, hogy gyakorlatilag bármilyen inputtal meg tudjon birkózni. Az Excel fájlokat a readxl csomagot használva tudjuk betölteni a read_excel()
függvény használatával.lásd ehhez a Függeléket A leggyakoribb statisztikai programok formátumait pedig a haven
csomag tudja kezelni (például Stata, Spss, SAS). A szintaxis itt is hasonló: read_stata()
, read_spss()
, read_sas()
.
A nagy mennyiségű szöveges dokumentum (a legyakrabban előforduló kiterjesztések: .txt
, .doc
, .pdf
, .json
, .csv
, .xml
, .rtf
, .odt
) betöltésére a legalkalmasabb a readtext
csomag. Az alábbi példa azt mutatja be, hogyan tudjuk beolvasni egy adott mappából az összes .txt
kiterjesztésű fájlt anélkül, hogy egyenként kellene megadnunk a fájlok neveit. A kódsorban szereplő *
karakter ebben a környezetben azt jelenti, hogy bármilyen fájl az adott mappában, ami .txt
-re végződik. Amennyiben a fájlok nevei tartalmaznak valamilyen metaadatot, akkor ezt is be tudjuk olvasni a betöltés során. Ilyen metaadat lehet például egy parlamenti felszólalásnál a felszólaló neve, a beszéd ideje, a felszólaló párttagsága (például: kovacsjanos_1994_fkgp.txt
).
3.2 Az adatok exportálása
Az adatainkat R-ből a write.csv()
-vel exportálhatjuk a kívánt helyre, .csv
formátumba. Az openxlsx
csomaggal .xls
és .xlsx
Excel formátumokba is tudunk exportálni. Az R rendelkezik saját, .Rds
és .Rda
kiterjesztésű, tömörített fájlformátummal. Mivel ezeket csak az R-ben nyithatjuk meg, érdemes a köztes, hosszadalmas számítást igénylő lépések elmentésére használni, a saveRDS()
és a save()
parancsokkal.
3.3 A pipe operátor
Az úgynevezett pipe
operátor alapjaiban határozta meg a modern R fejlődését és a népszerű csomag ökoszisztéma, a tidyverse
, egyik alapköve. Úgy gondoljuk, hogy a tidyverse
és a pipe
egyszerűbbé teszi az R használatának elsajátítását, ezért mi is erre helyezzük a hangsúlyt.3 Vizuálisan a pipe operátor így néz ki: %>%
(a pipe operátor a Ctrl + Shift + M
billentyúk kombinációjával könnyedén kiírható) és arra szolgál, hogy a kódban több egymáshoz kapcsolódó műveletet egybefűzzünk.4 Technikailag a pipe
a bal oldali elemet adja meg a jobb oldali függvény első argumentumának. A lenti példa, amely nem tartalmaz igazi kódot, csupán a logikát kívánja szemlélteni ugyanazt a folyamatot írja le az alap R (base R), illetve a pipe
használatával. 5 Miközben a kódot olvassuk, érdemes a pipe
-ot „és aztán”-nak fordítani.
reggeli(oltozkodes(felkeles(ebredes(en, idopont = "8:00"), oldal = "jobb"), nadrag = TRUE, ing = TRUE))
en %>%
ebredes(idopont = "8:00") %>%
felkeles(oldal = "jobb") %>%
oltozkodes(nadrag = TRUE, ing = TRUE) %>%
reggeli()
A fenti példa is jól mutatja, hogy a pipe
a bal oldali elemet fogja a jobb oldali függvény első elemének berakni. A pipe operátor működését még egy soron demonstrálhatjuk ez alkalommal valódi kódsorokkal és funkciókkal. Láthatjuk, hogy a pipe
baloldalán elhelyezkedő kiindulópont egy számsor, ezt először a sum()
funkcióba helyezzük, az így kapott eredmény 16 lesz, amelynek a gyökét vesszük az sqrt()
az operátor alkalmazásával ezt a két műveletet egyetlen kódsorban könnyen olvasható módon végezzük el és megkapjuk a teljes folyamat eredeményét vagyis négyet.
A fejezet további részeiben még bőven fogunk gyakorlati példát találni a pipe
használatára. Mivel az itt bemutatott példák az alkalmazásoknak csak egy relatíve szűk körét mutatják be, érdemes átolvasni a csomagokhoz tartozó dokumentációt, illetve ha van, akkor tanulmányozni a működést demonstráló bemutató oldalakat is.
3.4 Műveletek adattáblákkal
Az adattábla (data frame) az egyik leghasznosabb és leggyakrabban használt adattárolási mód az R-ben (a részletesebb leírás a Függelékben található). Ebben az alfejezetben azt mutatjuk be a dplyr
és gapminder
csomagok segítségével, hogyan lehet vele hatékonyan dolgozni. A dplyr
az egyik legnépszerűbb R csomag, a tidyverse
része. A gapminder
csomag pedig a példa adatbázisunkat tartalmazza, amiben a világ országainak különböző gazdasági és társadalmi mutatói találhatók.
A sorok (megfigyelések) szűréséhez a dplyr
csomag filter()
parancsát használva lehetőségünk van arra, hogy egy vagy több kritérium alapján szűkítsük az adatbázisunkat. A lenti példában azokat a megfigyeléseket tartjuk meg, ahol az év 1962 és a várható élettartam több mint 72 év.
gapminder %>%
dplyr::filter(year == 1962, lifeExp > 72)
#> # A tibble: 5 × 6
#> country continent year lifeExp pop gdpPercap
#> <fct> <fct> <int> <dbl> <int> <dbl>
#> 1 Denmark Europe 1962 72.4 4646899 13583.
#> 2 Iceland Europe 1962 73.7 182053 10350.
#> 3 Netherlands Europe 1962 73.2 11805689 12791.
#> 4 Norway Europe 1962 73.5 3638919 13450.
#> 5 Sweden Europe 1962 73.4 7561588 12329.
Ugyanígy leválogathatjuk az adattáblából az adatokat akkor is, ha egy karakter változó alapján szeretnénk szűrni.
gapminder %>%
filter(country == "Sweden", year > 1990)
#> # A tibble: 4 × 6
#> country continent year lifeExp pop gdpPercap
#> <fct> <fct> <int> <dbl> <int> <dbl>
#> 1 Sweden Europe 1992 78.2 8718867 23880.
#> 2 Sweden Europe 1997 79.4 8897619 25267.
#> 3 Sweden Europe 2002 80.0 8954175 29342.
#> 4 Sweden Europe 2007 80.9 9031088 33860.
Itt tehát az adattábla azon sorait szeretnénk látni, ahol az ország megegyezik a „Sweden” karakterlánccal, az év pedig 1990 utáni.
A select()
függvény segítségével válogathatunk oszlopokat a data frame-ből. A változók kiválasztására több megoldás is van. A dplyr
csomag tartalmaz apróbb kisegítő függvényeket, amik megkönnyítik a nagy adatbázisok esetén a változók kiválogatását a nevük alapján. Ezek a függvények a contains()
, starts_with()
, ends_with()
, matches()
, és beszédesen arra szolgálnak, hogy bizonyos nevű változókat ne kelljen egyenként felsorolni. A select()
-en belüli változó sorrend egyben az eredmény data frame változójának sorrendjét is megadja. A negatív kiválasztás is lehetséges, ebben az esetben egy -
jelet kell tennünk a nem kívánt változó(k) elé (pl.: select(df, year, country, -continent
).
gapminder %>%
dplyr::select(dplyr::contains("ea"), dplyr::starts_with("co"), pop)
#> # A tibble: 1,704 × 4
#> year country continent pop
#> <int> <fct> <fct> <int>
#> 1 1952 Afghanistan Asia 8425333
#> 2 1957 Afghanistan Asia 9240934
#> 3 1962 Afghanistan Asia 10267083
#> 4 1967 Afghanistan Asia 11537966
#> 5 1972 Afghanistan Asia 13079460
#> 6 1977 Afghanistan Asia 14880372
#> # ℹ 1,698 more rows
Az így kiválogatott változókból létrehozhatunk és objektumként eltárolhatunk egy új adattáblát az objektumok részletesebb leírása a függelékben található Függelékben, amivel azután tovább dolgozhatunk, vagy kiírathatjuk például .csv
fájlba, vagy elmenthetjük a saveRDS
segítségével.
A saveRDS
segítségével elmentett fájlt később a readRDS()
függvénnyel olvashatjuk be, majd onnan folytathatjuk a munkát, ahol korábban abbahagytuk.
Az elemzési munkafolyamat elkerülhetetlen része, hogy új változókat hozzunk létre, vagy a meglévőket módosítsuk. Ezt a mutate()
-el tehetjük meg, ahol a szintaxis a következő: mutate(data frame, uj valtozo = ertekek)
. Példaként kiszámoljuk a svéd GDP-t (milliárd dollárban) 1992-től kezdve. A mutate()
alkalmazását részletesebben is bemutatjuk a szövegek előkészítésével foglalkozó fejezetben.
gapminder %>%
filter(country == "Sweden", year >= 1992) %>%
dplyr::mutate(gdp = (gdpPercap * pop) / 10^9)
#> # A tibble: 4 × 7
#> country continent year lifeExp pop gdpPercap gdp
#> <fct> <fct> <int> <dbl> <int> <dbl> <dbl>
#> 1 Sweden Europe 1992 78.2 8718867 23880. 208.
#> 2 Sweden Europe 1997 79.4 8897619 25267. 225.
#> 3 Sweden Europe 2002 80.0 8954175 29342. 263.
#> 4 Sweden Europe 2007 80.9 9031088 33860. 306.
Az adataink részletesebb és alaposabb megismerésében segítenek a különböző szintű leíró statisztikai adatok. A szintek megadására a group_by()
használható, a csoportokon belüli számításokhoz pedig a summarize()
. A lenti példa azt illusztrálja, hogy ha kontinensenként csoportosítjuk a gapminder
adattáblát, akkor a summarise()
használatával megkaphatjuk a megfigyelések számát, illetve az átlagos per capita GDP-t. A summarise()
a mutate()
közeli rokona, hasonló szintaxissal és logikával használható. Ezt a függvénypárost fogjuk majd használni a szöveges adataink leíró statisztikáinál is az 5. fejezetben.
gapminder %>%
dplyr::group_by(continent) %>%
dplyr::summarise(megfigyelesek = n(), atlag_gdp = mean(gdpPercap))
#> # A tibble: 5 × 3
#> continent megfigyelesek atlag_gdp
#> <fct> <int> <dbl>
#> 1 Africa 624 2194.
#> 2 Americas 300 7136.
#> 3 Asia 396 7902.
#> 4 Europe 360 14469.
#> 5 Oceania 24 18622.
3.5 Munka karakter vektorokkal6
A szöveges adatokkal (karakter stringekkel) való munka elkerülhetetlen velejárója, a vektorokról, köztük a karakter vektorokról részletesebben a Függelékben írunk. A felesleges szövegelemeket, karaktereket el kell távolítanunk, hogy javuljon az elemzésünk hatásfoka. Erre a célra a stringr
csomagot fogjuk használni, kombinálva a korábban bemutatott mutate()
-el. A stringr
függvények az str_
előtaggal kezdődnek és eléggé beszédes nevekkel rendelkeznek. Egy gyakran előforduló probléma, hogy extra szóközök maradnak a szövegben, vagy bizonyos szavakról, karakterkombinációkról tudjuk, hogy nem kellenek az elemzésünkhöz. Ebben az esetben egy vagy több reguláris kifejezés (regular expression, regex) használatával tudjuk pontosan kijelölni, hogy a karakter sornak melyik részét akarjuk módosítani.7 A legegyszerűbb formája a regexeknek, ha pontosan tudjuk milyen szöveget akarunk megtalálni. A kísérletezésre az str_view()
-t használjuk, ami megjeleníti, hogy a megadott regex mintánk pontosan mit jelöl. A függvény match = TRUE
paramétere lehetővé teszi, hogy csak a releváns találatokat kapjuk vissza.
szoveg <- c("gitar", "ukulele", "nagybogo")
stringr::str_view(szoveg, pattern = "ar")
#> [1] │ git<ar>
Reguláris kifejezésekkel rákereshetünk nem csak egyes elemekre (pl.: szavak, szótagok, betűk) hanem olyan konkrét esetekre is, amikor ezek egymás után fordulnak elő. Ilyenkor úgynevezett ngrammokat használunk, amelyek egy karakterláncban szereplő n-számú elem szekvenciája. A lenti példban ezeke működését egy úgynevezett bigrammal tehát egy n=2 értékű ngrammal mutatjuk be. Az ngrammok segítenek kezelni olyan eseteket, amikor két egymást követő elem eltérő jelentéssel bír, egymás mellett, mint külön-külön.
szoveg <- c("a fehér ház fehérebb mint bármely más ház")
stringr::str_view(szoveg, pattern = "fehér ház")
#> [1] │ a <fehér ház> fehérebb mint bármely más ház
Az ún. „horgonyokkal“, (anchor) azt lehet megadni, hogy a karakter string elején vagy végén szeretnénk-e egyezést találni. A string eleji anchor a ^
, a string végi pedig a $
.
Továbbá azt is meghatározhatjuk, hogy egy adott karakter, vagy karakter kombinációt, valamint a mellett elhelyezkedő karaktereket is szeretnénk kijelölni.
Egy másik jellemző probléma, hogy olyan speciális karaktert akarunk leírni a regex kifejezésünkkel, ami amúgy a regex szintaxisban használt. Ilyen eset például a .
, ami mint írásjel sokszor csak zaj, ám a regex kontextusban a „bármilyen karakter„ megfelelője. Ahhoz, hogy magát az írásjelet jelöljük, a \\
-t kell elé rakni, ennek egy alternatívája, hogy a keresett szóláncot a fixed()
funkcióba helyezzük, így pedig a szóláncban lévő karakterek különleges jelentéseit figyelmen kívül hagyja és csak a magára az egzakt szóláncra keres rá. Ha rákeresünk a .
karakterre, akkor látható, hogy a szólánc mindegyik karakterét kijelöltük, hiszen a .
jelentése “bármilyen karakter”.
A \\
jel segítségével már csak a szövegben lévő tényleges pontokra keresünk rá.
Ugyanúgy a fixed()
funkció segítségével szintén csak a tényleges pontokra keresünk rá.
Néhány hasznos regex kifejezés:
[:digit:]
- számok (123)[:alpha:]
- betűk (abc ABC)[:lower:]
- kisbetűk (abc)[:upper:]
- nagybetűk (ABC)[:alnum:]
- betűk és számok (123 abc ABC)[:punct:]
- központozás (.!?\(){}
)[:graph:]
- betűk, számok és központozás (123 abc ABC.!?\(){}
)[:space:]
- szóköz ( )[:blank:]
- szóköz és tabulálás[:cntrl:]
- kontrol karakterek (\n
,\r
, stb.)*
- bármi
A tidyverse megközelítés miatt a kötetben szereplő R kód követi a “The tidyverse style guide” dokumentációt (https://style.tidyverse.org/)↩︎
Az RStudio-ban a pipe operátor billentyű kombinációja a
Ctrl + Shift + M
↩︎Köszönjük Andrew Heissnek a kitűnő példát.↩︎
A könyv terjedelme miatt ezt a témát itt csak bemutatni tudjuk, de minden részletre kiterjedően nem tudunk elmélyülni benne. A témában nagyon jól használható online anyagok találhatóak az RStudio GitHub tárhelyén (https://github.com/rstudio/cheatsheets/raw/master/strings.pdf), illetve Wickham and Grolemund (2016) 14. fejezetében.↩︎
A reguláris kifejezés egy olyan, meghatározott szintaktikai szabályok szerint leírt karakterlánc (string), amivel meghatározható stringek egy adott halmaza. Az ilyen kifejezés valamilyen minta szerinti szöveg keresésére, cseréjére, illetve a szöveges adatok ellenőrzésére használható. További információ: http://www.regular-expressions.info/↩︎