Slovník | Vyhledávání | Mapa webu
 
Základy informatiky pro biologyCvičebnice jazyka R 2. cvičení – Datové typy v jazyce R

2. cvičení – Datové typy v jazyce R

Datové typy v jazyce R

Cíle 2. cvičení: načtení dat ze souboru .xlsx, práce s datovými rámci, sloupci a řádky, frekvenční tabulky, popisné statistiky.

K datům z 1. cvičení obsahujím informace o měření polychlorovaných bifenylů (PCB) přibude další datová tabulka obsahující hodnoty různých datových typů. Vaším úkolem bude načíst data ze souboru MS Excelu (.xlsx), zjistit a přizpůsobit jejich datové typy, vytvořit frekvenční tabulku a spočítat nad daty jejich základní popisné statistiky.


Použití balíku xlsx

Nejefektivnějším řešením pro načtení dat z formátu .xlsx je použití balíku prozaicky pojmenovaného xlsx, který ovšem vyžaduje v počítači nainstalovaný Java SE Runtime Environment (v případě 64-bitového počítače v obou variantách pro 32 i 64 bitové stroje). Po stažení a instalaci obou javovských balíků lze jednoduše instalovat a připojit balík xlsx příkazem:

install.packages("xlsx")
library("xlsx")

Následně můžeme pomocí funkce read.xlsx načíst data z .xlsx souboru. Název souboru je prvním argumentem, druhým poviiným argumentem je pak pořadové číslo nebo jméno listu (oba řádky v následujícím příkladu provedou tutéž operaci):

brno3<-read.xlsx("brno.xlsx",1)
brno3<-read.xlsx("brno.xlsx","List1")

V případě obsáhlejších datových souborů může být použití funkce read.xlsx náročné na čas a operační paměť počítače. V takovém případě se nabízí využití alternativní varianty read.xlsx2, která využívá funkce readColumns a přesouvá větší část výpočtů spojených s načtením dat do prostředí Javy. Výsledek (datové typy sloupců) se díky tomu může mírně lišit od výsledku při použití funkce read.xlsx, pro tabulky nad 100 000 řádků je nicméně načítání až o řád rychlejší.


Vybrané datové typy objektů v R

Programovací jazyk R nabízí v základní distribuci několik datových typů a tříd. Nevhodně zvolený datový typ/třída bývá jedním z nejobvyklejších problémů, které řeší méně zkušení uživatelé při práci v jazyce R a proto je vhodné se datovým typům a třídám věnovat podrobněji před dalším výkladem.

Datové typy vycházejí z reprezentace proměnných v paměti počítače a díky tomu jsou pro většinu programovacích jazyků podobné. Na rozdíl od tříd jsou datové typy konstruovány jako co nejuniverzálnější. Pro konkrétní specifickou potřebu je potom možné z těchto datových typů konstruovat složitější třídy (classes) ve smyslu tříd v objektově orientovaném programování (s dědičnými vlastnostmi apod.).

Základní datové typy, se kterými je možné se setkat na uživatelské úrovni při práci v R, jsou double, complex, character, logical, integer, raw nebo list, NULL, closure (pro funkce), special a builtin. Některé z nich jsou podrobněji popsány v následujícím textu.

Datový typ double

Typ double je nejobvyklejším datovým typem, který slouží v jazyce R pro ukládání číselných hodnot reálných (desetinných) čísel. Datový typ double ukládá čísla ve formátu s plovoucí desetinnou čárkou (floating point) a tzv. dvojitou přesností (datový typ double je v R totožný s třídou numeric a dříve používanou třídou real), tj. v paměti zaujímá 64 bitů. Jazyk R nemá k dispozici datový typ, který by odpovídal reálným číslům s tzv. jednoduchou přesností (32 bitů).

Mimo reálná čísla může proměnná typu double nabývat také zvláštních hodnot ±Int (kladné a záporné nekonečno), NA (not available = hodnota není známa) či NaN (not a number = hodnota není přípustná, např. při dělení nulou).

Vytvořit lze proměnnou typu double jednoduše pomocí příkazu double (vrátí vektor nul o zadané délce) nebo vlastním přiřazením reálného čísla do proměnné:

promenna<-double(1)
promenna<-sqrt(2)

Stejně jako ve všech následujících příkladech lze ověřit pomocí funkce is.double, zda je daná proměnná typu double (funkce vrací logickou hodnotu TRUE nebo FALSE):

is.double(promenna)

Datový typ character

Datový typ character je určen pro ukládání textových řetězců v závislosti na zvoleném kódování. Počet znaků textového řetězce je libovolný od 0 (i prázdná proměnná může být typu character) po 2 147 483 647 znaků (32-bitových).

Vytvořit proměnnou typu character lze jednoduše pomocí příkazu character (vrátí vektor prázdných řetězců o zadané délce) nebo vlastním přiřazením textového řetězce do proměnné:

promenna<-character(1)
promenna<-"miluji jazyk R"

Opět lze ověřit, zda je proměnná typu character, pomocí funkce is.character:

is.character(promenna)

Datový typ logical

Jak napovídá už sám název, slouží datový typ logical k ukládání logických hodnot TRUE (pravda, ve většině aplikací v R lze také psát jako T, nicméně ne vždy) a FALSE (nepravda, obvykle lze psát pouze F):

promenna<-logical(4)
promenna<-F

Jako v předchozích případech, i zde je možné využít ověřovací funkci is.logical:

is.logical(promenna)

Další datové typy v R

Ze zbývajících datových typů uveďme ve stručnosti ještě univerzální typ list, určený pro ukládání seznamů, typ integer sloužící pro ukládání celých čísel, ke kterému se váže stejně pojmenovaná třída, typ complex opět se stejně pojmenovanou třídou, sloužící pro práci s komplexními čísly a typy closure a builtin, které slouží k ukládání funkcí a pojí se mj. se třídou function (povědomí o existenci těchto datových typů je přinejmenším vhodné pro porozumění chybovým hláškám při obvyklých chybách při psaní kódu).

Pro zjištění datového typu objektu slouží v R jednoduchá funkce typeof():

promenna<-as.double(1)
typeof(promenna)
typeof(integer(0))
typeof(5)
typeof(data.frame)
typeof(c)
typeof(NULL)


Vybrané třídy objektů v R

V mnoha případech při ukládání hodnot do proměnných nevystačíme se základními datovými typy a potřebujeme data uspořádat do složitější struktury. Proto je možné ze základních datových typů konstruovat složitější třídy (classes), které přiřazujeme jednotlivým objektům. Na rozdíl od datových typů existuje tříd nepřeberné množství a v případě potřeby navíc běžně uživatelé definují další třídy. Uvedeme proto pouze několik nejdůležitějších a nejčastěji používaných tříd v R.

Podobně jako pro zjištění datového typu objektu využíváme funkci typeof(), zjišťování třídy objektu budeme provádět pomocí funkce class().

Třída numeric

Třída numeric je totožná s datovým typem double a umožňuje tedy ukládání reálných čísel ve formátu s plovoucí desetinnou čárkou a dvojitou přesností a speciálních hodnot zmíněných výše.

Vytvořit lze proměnnou třídy numeric jednoduše pomocí příkazu numeric (vrátí vektor nul o zadané délce) nebo vlastním přiřazením reálného čísla do proměnné:

promenna<- numeric(1)
promenna<-sqrt(2)

Pomocí funkce is.numeric lze ověřit, zda je daná proměnná typu numeric (funkce vrací logickou hodnotu):

is.numeric(promenna)
typeof(promenna)
class(promenna)

Třída character

Obdobně jako třída numeric odpovídá datovému typu double, váže se k datovému typu character stejně pojmenovaná třída.

Vytvořit lze proměnnou třídy character jednoduše pomocí příkazu character (vrátí vektor prázdných řetězců o zadané délce) nebo vlastním přiřazením textového řetězce do proměnné:

promenna<-character(1)
promenna<-"miluji jazyk R"

Opět lze ověřit, zda je proměnná třídy character pomocí funkce is.character:

is.character(promenna)

Třída Date

Poměrně často je v praxi třeba zpracovávat údaje o kalendářním datu a čase. Zápis kalendářního data se v různém software a různých kulturních prostředích významně liší a tyto odlišnosti bývají jedním z nejčastějších zdrojů problémů při zpracování dat.

Datum je v prostředí R obvykle ukládáno jako typ double, existuje ale několik variant tříd, které nad tímto datovým typem vytváří nadstavbu se srozumitelnou interpretací. Nejjednodušší takovou třídou je třída Date, která ukládá datum jako počet dní od 1. ledna 1970. Výstupní formát je pak v podobě RRRR-MM-DD. V následujících příkladech si vystačíme při práci s kalendářními daty vždy se třídou Date, nicméně zájemci mohou prostudovat rovněž dostupné třídy POSIXlt a POSIXct.

Přiřazení třídy Date se provádí jednoduchou funkcí as.Date(), případně je automatické při vložení datové hodnoty do proměnné např. z funkce pro aktuální systémové datum Sys.Date():

promenna<-as.Date("1970-01-02")
promenna
promenna<-Sys.Date()
typeof(promenna)

Třída factor

Zajímavou třídou založenou na datovém typu integer je třída factor, sloužící ke kódování kategoriálních proměnných. Pro objekt třídy factor lze nadefinovat jednotlivé faktorové úrovně, které jsou následně očíslovány dle pořadí a prvky objektu jsou poté reprezentovány pořadovými čísly. To je vhodnější, než reprezentovat například konkrétní množinu barev jejich celými názvy:

promenna<-factor(c("zluta","modra","zluta","bila"))
promenna
as.integer(promenna)
typeof(promenna)

Se třídou factor se lze setkat často při importu dat, kde jsou proměnné některých datových typů načítány implicitně jako factor a pro další práci je obvykle nutné je konvertovat na jinou třídu.

Třída data.frame

Složitější strukturou, reprezentující dvourozměrně uspořádaná data je třída data.frame (datový rámec), reprezentovaná v podstatě tabulkou, v níž každý sloupec je představován jedním vektorem (všechny musí být stejné délky) s vlastní třídou (třídy vektorů v datovém rámci se mohou lišit). Vytvářet lze datové rámce různými způsoby, nejjednodušší je využití funkce data.frame() nebo konverzní funkce as.data.frame():

promenna<-data.frame(c("chlor","brom","jód"),c(35.45,79.90,126.90))
promenna<-data.frame(prvek=c("chlor","brom","jód"),hmotnost=c(35.45,79.90,126.90))
promenna<-as.data.frame(c(1,2,3))

Na buňky rámce se pak lze odkazovat pomocí dvourozměrného indexování ve formě ramec[radek,sloupec]. Pro ověření třídy lze stejně jako v předchozích případech využít funkci is.data.frame():

is.data.frame(promenna)
class(promenna)
typeof(promenna)

Třída matrix

Jiná, podobná, dvourozměrná struktura je definována jako třída matrix (matice). Většina parametrů datových rámců a matic je totožná, hlavní rozdíl spočívá ve větší efektivitě uložení a zpracování dat ve formě matice. Současně ale matice musí splňovat podmínku stejného datového typu všech svých prvků (tedy řádků i sloupců), naproti tomu datový rámec může mít každý sloupec jiného datového typu.

Jednoduše lze matici nadefinovat pomocí funkce matrix():

promenna<-matrix(TRUE,4,3)
data.frame(c("chlor","brom","jód"),c(35.45,79.90,126.90))


Vybrané konverze mezi třídami

Ani v případě velmi dobře nedefinovaných tříd objektů se nelze vyhnout potřebě měnit třídy některých objektů (proměnných), zejména ve fázi importu a exportu dat. Může jít například o výpis číselných hodnot v textových řetězcích, konverzi kalendářních data zadaných ve formě textu nebo čísel, změnu matice na datový rámec aj.

V řadě případů je konverze mezi třídami zřejmá (např. konverze celých čísel (integer) na reálná (numeric) a dále na komplexní (complex) je intuitivním vnořením číselných těles), v jiných případech je zřejmá méně a v některých případech je přímo nemožná (např. konverze textu na binární hodnoty).

Konvertovat celá čísla na reálná a reálná na komplexní lze snadno pomocí funkcí as.numeric() a as.complex(), zajímavější je situace v případě konverze opačného směru. Funkce as.numeric() a as.integer() si poradí i v tomto případě, nicméně nejprve dojde k ořezání imaginární části a poté části desetinné:

promenna<-c(0,2+3i,-1i)
class(promenna)
as.numeric(promenna)

Výsledkem bude vektor reálných čísel 0, 2, 0, neboť imaginární část je úplně zanedbána.

Obdobně dojde k ořezání neceločíselné části (tj. zaokrouhlení nahoru pro záporná a dolů po kladná čísla) v případě konverze reálných čísel na celá:

promenna<-c(1,2.2,9.99,-3.5,Inf)
class(promenna)
as.integer(promenna)

Podobně lze pokračovat k binární třídě logical, kde se všechny hodnoty kromě nuly interpretují jako pravda a nula jako nepravda.

Zdánlivě jednoduchá je konverze reálných čísel na textové řetězce. Zde je pouze zapotřebí hlídat počet desetinných míst, která budou vytištěna. Počet platných cifer v tomto případě (na rozdíl od výpisu na konzoli) není ovlivněn globálním nastavením přesnosti pomocí funkce options(digits=pocet_cifer)):

as.character(c(0.142857142857142857142857142857,1/7,1000/7,pi))

Je tedy vhodné použít některou ze zaokrouhlovacích funkcí pro požadovaný počet desetinných míst (např. na 3 desetinná místa):

as.character(round(c(0.142857142857142857142857142857,1/7,1000/7,pi),3))

Užitečná konverze je ve směru z třídy factor na třídu numeric, v případě, že proměnná třídy factor ukrývá původní reální čísla. Navíc přímo v rámci konverze lze zpracovat nevhodně kódované hodnoty (např. nečíselné hodnoty v číselné proměnné aj.). Nicméně je třeba mít na paměti, že třída factor je založena na datovém typu integer – velmi nebezpečnou chybou totiž může být v tomto kroku pokus o přímou konverzi:

promenna<-factor(c(1.5,-1.4,2.0,0.9))
as.numeric(promenna)

V tomto případě se konvertují na třídu numeric pořadová čísla faktorových úrovní datového typu integer a výsledkem bude naprosto nesmyslný vektor čísel 3, 1, 4, 2. Nebezpečí chyby spočívá především v její špatné odhalitelnosti, protože konverze je úspěšně provedena s nesmyslnými hodnotami bez oznámení chyby. Tento problém lze obejít poměrně snadno postupnou konverzí přes třídu character:

promenna<-factor(c(1.5,-1.4,2.0,0.9))
as.numeric(as.character(promenna))

Konverze textu (character) na kalendářní datum (Date) je jednoduchá v případě, kdy jsou k dispozici textové řetězce formátu RRRR-MM-DD, funkce as.Date() si poradí i s řetězci tvaru RRRR-M-D, nicméně neumí zpracovat neúplná kalendářní data a letopočty před rokem 0:

promenna<-as.Date(c("2015-09-09","2015-9-9","0-1-1","-15-2-6","2015"))

promenna<-as.Date(c(16800),origin="1970-01-01")

Bezproblémová bývá konverze z matice na datový rámec:

matice<-matrix(c("chlor","brom","jód",
"síra","selen","telur",
"fosfor","arsen","antimon"),3,3)
as.data.frame(matice)

V opačném směru je zapotřebí pohlídat, aby byly všechny sloupce vhodné třídy, konvertovatelné na výslednou třídu prvků matice:

ramec<-data.frame(c("chlor","brom","jód"),c(35.45,79.90,126.90))
as.matrix(ramec)

Poslední užitečnou ukázkou je konverze více sloupců datového rámce najednou, pro niž nelze použít přímo příkaz pro konverzi, neboť by se narušila struktura rámce. Opět je nutné využít funkce lapply() a konvertovat každý sloupec zvlášť jako v následujícím (a v praxi dosti obvyklém) příkladu pro konverzi sloupců tříd factor na numeric:

ramec<-data.frame(factor(c(1,5.5,99)),factor(c(-1,-6,-100)))
lapply(ramec,class)
lapply(lapply(ramec,as.character),as.numeric)


Popisné statistiky

Datovou třídu proměnné je třeba zohlednit při výběru vhodných popisných statistik pro charakterizaci proměnné. Výběr nevhdoné popisné statistiky nejen, že může poskytnout její nesmyslnou hodnotu, ale může vést i k případu, kdy daná funkce nebude schopna proměnnou dané datové třídy vůbec zpracovat.

Mezi základní popisné statistiky, uvádějící „typickou“ či „středovou“ hodnotu proměnné patří modus (tj. nejčastěji se vyskytující hodnota v souboru) v případě kategoriálních (vč. binárních) proměnných, medián v případě ordinálních proměnných (včetně všech číselných proměnných) a ve vhodných případech číselných proměnných také aritmetický či geometrický průměr.

Pro aritmetický průměr a medián jsou v R k dispozici přímo napsané funkce mean() a median():

ramec<-data.frame("prvek"=c("fluor","chlor","brom","jód","astat"),
"hmotnost"=c(19.00,35.45,79.90,126.90,209.99))
mean(ramec$hmotnost)
median(ramec$hmotnost)

Obdobně lze pro charakterizaci rozpětí (rozptylu) proměnné využít funkce min a max pro extrémní hodnoty, sd pro směrodatnou odchylku a quantile pro kvantily - tato poslední funkce navíc obsahuje další argument probs udávající, kolikáté kvantily má obsahovat návratový vektor. Všechny zmíněné funkce navíc obsahují volitelný (logický) argument NA.rm, specifikující, zda mají být z výpočtu vyloučeny neznámé hodnoty. Výchozí nastavení NA.rm=FALSE vrací hodnotu NA ve všech případech, kdy nejméně jedna hodnota v původní proměnné chybí.

Funkce modus a geometrický průměr přímo k dispozici nejsou, a proto se k nim vrátíme v kapitole věnované tvorbě vlastních funkcí. Namísto toho lze pro kategoriální proměnné efektivně využít funkcí pro tvorbu frekvenčních (v případě jedné proměnné) a kontingenčních (v případě více proměnných) tabulek table a ftable. Obě funkce fungují podobně, liší se pouze návratovou strukturou, kde lze zjednodušeně tvrdit, že pro frekvenční tabulky a kontingenční tabulky dvou proměnných je vhodnější funkce table s návratovou třídou table, zatímco v případě více proměnných je efektivnější využití funkce ftable s návratovou třídou ftable (flat table):

ramec<-data.frame("prvek"=c("fluor","chlor","brom","jód","astat"),
"barva"=c("žlutozelená","žlutozelená","červená","červená",NA),
"hmotnost"=c(19.00,35.45,79.90,126.90,209.99))
table(ramec$prvek)
table(ramec$prvek,ramec$barva)
ftable(ramec$prvek,ramec$barva)

Ve výše uvedeném příkladu stojí za zmínku hodnota NA označující neznámou hodnotu (not available). Astat byl dosud vyroben vždy pouze v množstvích pouhých několika atomů jadernými reakcemi a jeho barva proto není známá (předpokládá se, že jako pravděpodobný polokov je astat černý nebo kovově lesklý). Zacházení s takovými hodnotemai ošetřuje v případě funkce table její argument useNA, který v základním nastavení hodnoty NA ignoruje (useNA="no"). Nastavení argumentu useNA na hodnotu ifany zobrazí ve výsledné tabulce speciální sloupec NA, pokud se tyto hodnoty v datech vyskytují a nastavení always ponechá tento sloupec ve výsledné tabulce vždy:

ramec<-data.frame("prvek"=c("fluor","chlor","brom","jód","astat"),
"barva"=c("žlutozelená","žlutozelená","červená","červená",NA),
"hmotnost"=c(19.00,35.45,79.90,126.90,209.99))
table(ramec$prvek,ramec$barva,useNA="ifany")

Na závěr uveďme příklad, ve kterém se vyskytují hodnoty v řádcích a sloupcích opakovaně a výsledná kontingenční tabulka tedy skutečně poslouží k posouzení frekvence jednotlivých hodnot a vzájemného vztahu proměnných barva a ox.číslo:

ramec<-data.frame("prvek"=c(rep("fluor",2),rep("chlor",9),rep("brom",7),
rep("jód",6),rep("astat",6)),
"barva"=c(rep("žlutozelená",11),rep("červená",13),rep(NA,6)),
"ox.číslo"=c("-I","0","-I","0","I","II","III","IV","V","VI","VII","-I","0","I","III","IV","V","VII","-I","0","I","III","V","VII","-I","0","I","III","V","VII"))
table(ramec$barva,ramec$ox.číslo,useNA="ifany")


Cvičení

1. Pro následující cvičení si stáhněte soubor brno.xlsx obsahující záznamy o naměřených koncentracích polychlorovaných bifenylů ve městě Brně mezi roky 2003 a 2014. Uložte soubor na disk do složky, ve které budete i nadále pracovat, a nastavte tuto složku jako pracovní.

2. Nainstalujte a načtěte balík xlsx pro práci s excelovskými soubory. V případě, že obdržíte chybovou hlášku podobnou následující, nainstalujte korektně obě verze programovacího jazyka Java (na 64 bitových strojích pro 32 i 64 bitů):

Error: package or namespace load failed for ‘xlsx’.

3. Zvolte vhodnou funkci pro načtení prvního listu ze souboru brno.xlsx do proměnné (tj. datové tabulky) pojmenované brno. Po načtení zkontrolujte obsah proměnné a v případě nesprávně načtených dat zvolte jiný postup pro import listu.

4. Vypište názvy sloupců datové tabulky brno.

5. Ověřte datové třídy sloupců lokalita, zacatek, stred, konec, matrice, latka, jednotka, program, hodnota, limit_kvantifikace a tydenni. U kterých sloupců neodpovídá datová třída vhodně jejich obsahu?

6. U sloupců datové tabulky třídy factor rozhodněte o jejich konverzi na vhodnější třídu a tuto konverzi proveďte. Zkontrolujte konverzi zejména u sloupců limit_kvantifikace a tydenni.

7. Pokud jste tak dosud neučinili, konvertujte vhodným způsobem (je jich víc) sloupec tydenni datové tabulky brno na třídu logical a uložte hodnoty z tohoto sloupce do logického vektoru nazvaného tydenni. Cvičně tento vektor postupně konvertujte na datové třídy integer, numeric, complex, a character a pozorujte, jak se postupně mění jeho původní hodnoty.

8. Sestrojte frekvenční tabulku pro lokality (sloupec lokalita) a následně kontingenční tabulku pro lokality (v řádcích) a počáteční roky vzorkování (ve sloupcích, sloupec zacatek_rok datové tabulky brno).

9. Použijte funkci which () pro výběr pouze těch řádků z datové tabulky brno, které odpovídají látce PCB 180 a uložte tyto řádky do nové datové tabulky nazvané pcb180:

pcb180<-brno[which(brno$latka=="PCB180"),]

10. Pokud jste tak dosud neučinili, konvertujte sloupec hodnota datové tabulky pcb180 na datovou třídu numeric a spočtěte jeho medián, pátý a devadesátý pátý percentil.

 
vytvořil Institut biostatistiky a analýz Lékařské fakulty Masarykovy univerzity