A tanterv más részein többféle, eltérő tulajdonságú adattípussal találkoztunk.
Ez a fogalom azzal foglalkozik, hogyan tárolhatunk többdimenziós, heterogén adatokat. A gyakorlatban a valós adatok többsége ilyen, így most jutunk el annak a lényegéhez, hogy mire is használják (többnyire) az R-t.
Az évtizedek során az R több adattípust is bevezetett a táblázatos adatok kezelésére.
Ez a tanterv elsősorban a tibble-ökre összpontosít, de hasznos ismerni néhány alternatívát.
data.table
A tibble-ök (amelyeket alább mutatunk be) és a data.table-ok egyaránt elismert próbálkozások arra, hogy továbbfejlesszék a Base R hagyományos data.frame-jét.
Elkerülhetetlenül sok vita folyik arról, hogy melyik a „jobb”.
Talán némi egyetértés körvonalazódik a következő pontok körül (még ha leegyszerűsítőnek is fogják kritizálni őket).
tibble elsősorban a könnyű használhatóságra és a Tidyverse ökoszisztémával való integrációra van optimalizálva.data.table elsősorban a nyers teljesítményre és a skálázhatóságra van optimalizálva, különösen nagyon nagy adathalmazok esetén.Mindenesetre a data.table nem érhető el az Exercism-ön, ezért itt csak a teljesség kedvéért említjük meg.
data.frame
A Base R-ben a data.frame egyenlő hosszúságú vectors-okból álló list.
Felfoghatjuk egy téglalap alakú adattáblaként, amelyben minden oszlop homogén, de minden sor különböző típusú adatokat tartalmazhat (és általában tartalmaz is).
Nézzünk egy példát, amely ezt szemlélteti:
# create the column vectors
languages <- c("Fortran", "R", "Python", "Julia")
created <- c(1957, 1993, 1991, 2012)
has.syllabus <- c(FALSE, TRUE, TRUE, TRUE)
# join columns to create the dataframe
df <- data.frame(languages, created, has.syllabus)
df
#> languages created has.syllabus
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
# look at the structure
str(df)
#> 'data.frame': 4 obs. of 3 variables:
#> $ languages : chr "Fortran" "R" "Python" "Julia"
#> $ created : num 1957 1993 1991 2012
#> $ has.syllabus: logi FALSE TRUE TRUE TRUE
Van egy stringekből álló oszlopunk, egy számokból álló oszlopunk és egy Boolean-okból álló oszlopunk. Nagyobb léptékben ez intuitív módja annak, hogy a valós világ számos adatgyűjteményét ábrázoljuk.
tibble
A data.frame kialakítása régi.
A több évtizedes tapasztalat, valamint az R használatának változó mintái újratervezéshez vezettek, és létrehozták a Tidyverse modernizált alternatíváját: a tibble-öket.
A Base R-hez képest a tibble-ök:
Röviden: a tibble célja, hogy „kevesebbet tegyen és többet panaszkodjon”, amit úgy is szoktak jellemezni, hogy „lusta és mogorva”.
A típusok általában felcserélhetők: bármely függvény, amely elfogad egy data.frame-et, egy tibble-t is elfogad, és fordítva.
A típusok közötti átalakítás egyszerű, a as_tibble(df) és az as.data.frame(tbl) segítségével.
Új munkáknál a tibble-ök használata valószínűleg segít robusztusabb kódot írni.
Azonban az R világában rengeteg örökölt kód és örökölt adat van, így a data.frame valószínűleg még sokáig gyakori marad.
# column vectors are same as for data.frame
library(tibble)
tbl <- tibble(languages, created, has.syllabus)
tbl
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
str(tbl)
#> tibble [4 × 3] (S3: tbl_df/tbl/data.frame)
#> $ languages : chr [1:4] "Fortran" "R" "Python" "Julia"
#> $ created : num [1:4] 1957 1993 1991 2012
#> $ has.syllabus: logi [1:4] FALSE TRUE TRUE TRUE
Figyeld meg az alapértelmezett nyomtatási formátumot: a méreteket tartalmazó komment sor automatikusan kiíródik, és az oszloptípusok is megjelennek.
A tibble-ök a Tidyverse alappillérei, ezért a library(tibble) vagy a library(tidyverse) utasítással töltheted be őket.
A dokumentáció meglehetősen terjedelmes, a Tidyverse stílusában:
A legegyszerűbb, ha a tibble() függvénnyel fűzzük össze az oszlopvektorokat, ahogy a data.frame() esetében is.
Erre már láttunk példát egy korábbi szakaszban.
Ha kényelmesebb soronként megadni az értékeket, akkor a megfelelő függvény a tribble().
tbl_r <- tribble(
# column names marked with tilde prefix
~languages, ~created, ~has.syllabus,
"Fortran", 1957, FALSE,
"R", 1993, TRUE,
"Python", 1991, TRUE,
"Julia", 2012, TRUE
)
tbl_r
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
A gyakorlatban tucatnyi módja van a tibble-ök létrehozásának, mivel a Tidyverse sokféle függvényének ez az alapértelmezett kimeneti formátuma. Erre egy későbbi fogalomban még visszatérünk.
A funkcionális programozás fogalom a purrr könyvtárat mutatta be vektorok és listák (egydimenziós adatszerkezetek) kezelésére.
Adatkeretekhez (akár hagyományosak, akár tibble-ök) a megfelelő könyvtár a dplyr.
A dplyr-t korábban már bemutattuk, a switch fogalomban.
Ott csak néhány segédfüggvényt használtunk, de most elkezdhetjük felfedezni ennek a nagy könyvtárnak a többi részét.
Az adatkeretek, köztük a tibble-ök is, oszlopvektorok listájaként kezelhetők, így a lista indexelésével kiolvashatunk egy adott oszlopot.
tbl
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
tbl$created
#> [1] 1957 1993 1991 2012
Egy adatkeret mátrixstílusú indexeléssel is elérhető.
tbl[c(2, 4), 1:2]
# A tibble: 2 × 2
#> languages created
#> <chr> <dbl>
#> 1 R 1993
#> 2 Julia 2012
A modern R-ben, a Tidyverse ökoszisztémával a dplyr függvényei általában rugalmasabbak és kényelmesebbek, és ezek lesznek a fogalom hátralévő részének a középpontjában.
Egyetlen oszlopot a pull() függvénnyel kérhetsz le, a nevével vagy a sorszámával (a negatív számok jobbról balra számolnak).
tbl |> pull(created)
#> [1] 1957 1993 1991 2012
Ez ugyanazt az eredményt adja, mint a tbl$created, csak pipe-barát függvénnyel.
Több oszlop kiválasztásához a megfelelő függvény a select(), amely rendkívül sokoldalú.
Az oszlopokat a nevük vagy a típusuk tulajdonságai alapján kérheted le (vagy hagyhatsz el).
# Range with position and/or name
tbl |> select(1:created)
# A tibble: 4 × 2
#> languages created
#> <chr> <dbl>
#> 1 Fortran 1957
#> 2 R 1993
#> 3 Python 1991
#> 4 Julia 2012
# Exclude a column
tbl |> select(!created)
# A tibble: 4 × 2
#> languages has.syllabus
#> <chr> <lgl>
#> 1 Fortran FALSE
#> 2 R TRUE
#> 3 Python TRUE
#> 4 Julia TRUE
# Use type of column
tbl |> select(where(is.numeric))
# A tibble: 4 × 1
#> created
#> <dbl>
#> 1 1957
#> 2 1993
#> 3 1991
#> 4 2012
Több feltétel is megadható a &, | és ! logikai operátorokkal (és, vagy, nem).
Azok az oszlopnevek, amelyek érvényes R-azonosítók, nem igényelnek idézőjelet a select() függvényen belül.
Az érvénytelen neveket (például a szóközt tartalmazókat) backtickek közé zárhatod, bár talán jobb lenne átnevezni őket.
A select() függvény számos segédfüggvénnyel együttműködhet az oszlopnevek kiválasztásában: starts_with, contains, num_range és még sok más.
A matches teljes RegEx illesztést tesz lehetővé.
A részletekért lásd a dokumentációt.
Ez a sok lehetőség meglehetősen nevetségesnek tűnik a nyelvekről szóló játék-adatkeretünkön.
Szerencsére a starwars tibble megtalálható a dplyr-ben, így van min gyakorolni egy nagyobb adathalmazon.
# limit display to top 3 rows of non-list columns
starwars |>
select(!where(is.list)) |>
head(3)
# A tibble: 3 × 11
#> name height mass hair_color skin_color eye_color birth_year sex gender homeworld species
#> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <chr>
#> 1 Luke Skywalker 172 77 blond fair blue 19 male masculine Tatooine Human
#> 2 C-3PO 167 75 NA gold yellow 112 none masculine Tatooine Droid
#> 3 R2-D2 96 32 NA white, blue red 33 none masculine Naboo Droid
# pick a subset of columns
starwars |>
select(name | ends_with("color")) |>
head(5)
# A tibble: 5 × 4
#> name hair_color skin_color eye_color
#> <chr> <chr> <chr> <chr>
#> 1 Luke Skywalker blond fair blue
#> 2 C-3PO NA gold yellow
#> 3 R2-D2 NA white, blue red
#> 4 Darth Vader none white yellow
#> 5 Leia Organa brown light brown
A dplyr nyilvánvalóan hatékony módokat kínál az oszlopok név szerinti kiválasztására.
Tudunk vajon hasonlót tenni a sornevekkel is?
Nem!
A hagyományos R-adatkeretek rendelkezhetnek sornevekkel, de (a hibák és teljesítményproblémák hosszú története után) a sornevek nem megengedettek a tibbles-ben.
Ha neveket szeretnél, tedd őket egy <chr> oszlopba (általában az 1. oszlopba), és ugyanúgy használd, mint bármelyik másik oszlopot.
Az olyan importáló függvények, mint az as_tibble(), automatikusan létrehozzák ezt, amikor elnevezett sorokkal rendelkező adatokat importálsz.
Ha ez a sornév-korlátozás furcsán megkötőnek tűnik, gondolj arra, hogy a legtöbb nagy adatbázisrendszer ugyanígy kezeli a táblákat: Oracle, SQL Server, PostgreSQL, MySQL...
A feltételeknek megfelelő sorokat a filter() függvénnyel kapod meg, vagy a filter_out() függvénnyel zárod ki őket.
starwars |>
select(name:mass) |>
filter(between(height, 150, 165) & !is.na(mass))
# A tibble: 4 × 3
#> name height mass
#> <chr> <int> <dbl>
#> 1 Leia Organa 150 49
#> 2 Beru Whitesun Lars 165 75
#> 3 Nien Nunb 160 68
#> 4 Ben Quadinaros 163 65
A szűrési feltételek tetszőlegesen összetettek lehetnek, de mindig a sorok tartalmán alapulnak.
Ha ismertek a sorok sorszámai, különféle slice() függvényeket használhatunk.
starwars |>
select(name | homeworld) |>
slice(20:25)
# A tibble: 6 × 2
#> name homeworld
#> <chr> <chr>
#> 1 Palpatine Naboo
#> 2 Boba Fett Kamino
#> 3 IG-88 NA
#> 4 Bossk Trandosha
#> 5 Lando Calrissian Socorro
#> 6 Lobot Bespin
# random sample of rows
starwars |>
select(name | homeworld) |>
slice_sample(n = 4)
# A tibble: 4 × 2
#> name homeworld
#> <chr> <chr>
#> 1 Shaak Ti Shili
#> 2 Luminara Unduli Mirial
#> 3 Grievous Kalee
#> 4 Palpatine Naboo
A duplikált sorok eltávolításához a distinct() függvényt használd.
Először is egy figyelmeztetés: a copy-on-modify alapértelmezés miatt az eredeti tibble általában változatlan marad.
A legtöbb módosítás oszloponként történik.
Az oszlopneveket a rename(newname = oldname) függvénnyel változtathatod meg, vagy a rename_with() függvénnyel alkalmazhatsz rájuk egy függvényt.
Tipikus felhasználás az importált nevek megtisztítása, hogy könnyebb legyen velük R-ben dolgozni: a szóközök eltávolítása és az összetartozó nevek egységes formátumra hozása.
Figyeld meg a rename() szintaxisát.
Az oldname oszlop tartalma a newname névhez kötődik, innen a sorrend.
Az oszlopok sorrendjét a relocate() függvénnyel változtathatod meg.
A megadott oszlop(ok) alapértelmezés szerint a legbaloldalibb helyre kerülnek, de a .before vagy .after argumentummal finomabban szabályozhatod a pozíciót.
sw <- starwars |> select(name:species) |> slice(1:4)
sw
# A tibble: 4 × 11
#> name height mass hair_color skin_color eye_color birth_year sex gender homeworld species
#> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <chr>
#> 1 Luke Skywalker 172 77 blond fair blue 19 male masculine Tatooine Human
#> 2 C-3PO 167 75 NA gold yellow 112 none masculine Tatooine Droid
#> 3 R2-D2 96 32 NA white, blue red 33 none masculine Naboo Droid
#> 4 Darth Vader 202 136 none white yellow 41.9 male masculine Tatooine Human
sw |> relocate(c(species, homeworld), .after = name)
# A tibble: 4 × 11
#> name species homeworld height mass hair_color skin_color eye_color birth_year sex gender
#> <chr> <chr> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
#> 1 Luke Skywalker Human Tatooine 172 77 blond fair blue 19 male masculine
#> 2 C-3PO Droid Tatooine 167 75 NA gold yellow 112 none masculine
#> 3 R2-D2 Droid Naboo 96 32 NA white, blue red 33 none masculine
#> 4 Darth Vader Human Tatooine 202 136 none white yellow 41.9 male masculine
Nagyobb változtatásokhoz a mutate() lehetővé teszi, hogy:
NULL értékre állítodA mutate() nyilvánvalóan hatékony, potenciálisan zavaró, és ok arra, hogy nagyon hálásak legyünk a copy-on-modify-nak.
Nincs nyilvánvaló ok arra, hogy a Star Wars-szereplők testtömegindexével foglalkozzunk, de azért, ha mégis számítana:
starwars |>
select(c(name, species, height, mass)) |>
mutate(BMI = mass / (height / 100)^2) |>
head(4)
# A tibble: 4 × 5
#> name species height mass BMI
#> <chr> <chr> <int> <dbl> <dbl>
#> 1 Luke Skywalker Human 172 77 26.0
#> 2 C-3PO Droid 167 75 26.9
#> 3 R2-D2 Droid 96 32 34.7
#> 4 Darth Vader Human 202 136 33.3
Amikor a mutate()-hez hasonló függvényekkel az oszlopok egy részhalmazán szeretnél műveletet végezni, a fenti példában szereplő select() |> mutate() sorozat az egyik lehetőség.
Az eredményben csak a kiválasztott oszlopok lesznek benne.
Alternatívaként kényelmes lehet a pick() függvényt a mutate() híváson belül használni:
starwars |> mutate(pick(c(name, species, height, mass)), BMI = mass / (height / 100)^2) |> head(4)
When you want to operate on a subset of the columns with functions such as `mutate()`, the `select() |> mutate()` sequence in the above example is one option.
Only the selected columns will be in the result.
Alternatively, it can be convenient to use `pick()` _within_ the `mutate()` call:
```R
starwars |> mutate(pick(c(name, species, height, mass)), BMI = mass / (height / 100)^2) |> head(4)
# A tibble: 4 × 15
#> name height mass hair_color skin_color eye_color birth_year sex gender homeworld species films vehicles
#> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <chr> <lis> <list>
#> 1 Luke Skywal… 172 77 blond fair blue 19 male mascu… Tatooine Human <chr> <chr>
#> 2 C-3PO 167 75 NA gold yellow 112 none mascu… Tatooine Droid <chr> <chr>
#> 3 R2-D2 96 32 NA white, bl… red 33 none mascu… Naboo Droid <chr> <chr>
#> 4 Darth Vader 202 136 none white yellow 41.9 male mascu… Tatooine Human <chr> <chr>
# ℹ 2 more variables: starships <list>, BMI <dbl>
A módosításban csak a pick-elt oszlopok vesznek részt, de az összes oszlop visszakerül az eredménybe.
A soronkénti műveletek kevésbé gyakoriak egyetlen tibble módosításakor (több tibble összefűzéséről egy későbbi fogalomban lesz szó).
Egy kivétel: az arrange() egy vagy több oszlop értékei szerint rendezi a sorokat.
tbl
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
tbl |> arrange(languages)
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 Julia 2012 TRUE
#> 3 Python 1991 TRUE
#> 4 R 1993 TRUE
Az adatkeretek, akár hagyományosak, akár tibble-ök, központi szerepet játszanak a modern R tipikus használatában.
A Tidyverse függvényeinek a többsége (nem csak a dplyr) tibble-öket fogad bemenetként és/vagy hoz létre kimenetként.
Ez a fogalom csak egy rövid bevezetést adott, alig karcolva meg a lehetőségek felszínét.
A későbbi fogalmak az adatkeretek számos további aspektusát is tárgyalják (az Exercism technikai korlátain belül).