Kurzusok
/
R
R
/
Tanterv
/
Adatkeretek
Ad

Adatkeretek ebben a kurzusban: R

1 feladat

A(z) Adatkeretek fogalomról

A tanterv más részein többféle, eltérő tulajdonságú adattípussal találkoztunk.

  • Az atomi vektorok egydimenziósak, és típusukban homogének.
  • A listák egydimenziósak, elemeik pedig eltérő típusúak lehetnek.
  • A mátrixok és tömbök többdimenziósak és homogének.

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 adatkeretek változatai

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.

A 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).

  • A tibble elsősorban a könnyű használhatóságra és a Tidyverse ökoszisztémával való integrációra van optimalizálva.
  • A 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.

A 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.

A 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:

  • Más alapértelmezett beállításokat használnak, hogy csökkentsék a gyakori problémákat.
  • Kisebb hajlandóságot mutatnak az adattípusok automatikus konverziójára a bemenet feldolgozása közben.
  • Több és világosabb hibaüzenetet adnak.
  • Más, általában jobb megjelenítési formátummal rendelkeznek.

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.

Munka a tibble-ökkel

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:

Tibble létrehozása

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.

Egy tibble kezelése

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.

Részhalmazok kiválasztása

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.

Oszloponkénti műveletek

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    

Soronkénti műveletek

Note

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.

Egy tibble módosítása

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:

  • Új oszlopokat hozz létre, amelyek a meglévő oszlopok függvényei.
  • Lecserélj egy meglévő oszlopot úgy, hogy egy ugyanolyan nevű új oszlopot hozol létre.
  • Törölj egy oszlopot úgy, hogy az értékét NULL értékre állítod

A 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     

Összefoglalás

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).

Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg

Tanuld meg a(z) Adatkeretek fogalmat