Zpět na přehled cvičení

Rkový skript ke stažení: R

Co si zde představíme?

Třída character

Se stringy jsme se už setkali. Ohraničujeme je pomocí uvozovek, čímž v Rku vzniká objekt třídy character:

x <- "text"
class(x)
## [1] "character"
is.character(x)          # ověření, zda se jedná
## [1] TRUE
y <- character(5)        # vytvoření vektoru s prázdnými stringy
length(x); length(y)     # délka vektoru
## [1] 1
## [1] 5
nchar(x); nchar(y)       # délky jednotlivých stringů
## [1] 4
## [1] 0 0 0 0 0

Čísla lze konvertovat na text, obráceně jen tehdy, vznikne-li srozumitelné číslo:

as.character(123.456)                    # konverze čísla na string
## [1] "123.456"
as.numeric("123.456")                    # string na číslo  
## [1] 123.456
as.numeric("123,456")                    # nebere desetinnou čárku a vrací NA!
## Warning: NAs introduced by coercion
## [1] NA
format(123.456, digits = 4)              # další způsob s kontrolou počtu platných číslic (digits)
## [1] "123.5"
format(123.456, digits = 4, nsmall = 2)  # nsmall = minimální počet desetinných míst
## [1] "123.46"
format(123.456, decimal.mark = ",")      # desetinná čárka pro český text
## [1] "123,456"
format(123.456, scientific = TRUE)       # vědecký zápis čísla x.xxx * 10^{to za e}
## [1] "1.23456e+02"
format(c(123.4, 0.0256), digits = 2)     # pro srovnání na stejný počet desetinných míst zadat jako vektor
## [1] "123.400" "  0.026"

Na stringy také funguje řazení dle abecedy:

"a" < "b"
## [1] TRUE
c("A" < "b", "a" < "B")       # zjevně ignoruje velikost písmen
## [1] TRUE TRUE
"Adam" < "Aaron"              # samozřejmě srovnává delší slova dle abecedy
## [1] FALSE
"chleba" < "emental"          # při nastavení češtiny rozumí zařazení "ch"
## [1] FALSE
c("b"<"ch", "c"<"ch", "c"<"d", "ch"<"d", "ch"<"i", "ch" < "cecilie")
## [1]  TRUE  TRUE  TRUE FALSE  TRUE FALSE
Sys.setlocale("LC_ALL", "en")
## [1] "LC_COLLATE=en;LC_CTYPE=en;LC_MONETARY=en;LC_NUMERIC=C;LC_TIME=en"
"chleba" < "emental" 
## [1] TRUE
"ďábel" < "řehoř"             # v angličtině neznámé symboly
## [1] NA
Sys.setlocale("LC_ALL", "czech.utf8")
## [1] "LC_COLLATE=Czech_Czechia.utf8;LC_CTYPE=Czech_Czechia.utf8;LC_MONETARY=Czech_Czechia.utf8;LC_NUMERIC=C;LC_TIME=Czech_Czechia.utf8"
"chleba" < "emental" 
## [1] FALSE

Předefinované vektory velkých a malých písmen:

letters
##  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
LETTERS
##  [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z"

Základní práce se stringy

Začněme s transformacemi daného stringu:

x <- "MiXeD cAsE 123"
tolower(x)
## [1] "mixed case 123"
toupper(x)
## [1] "MIXED CASE 123"
casefold(x, upper = TRUE)   # funkce, která umí obojí
## [1] "MIXED CASE 123"
casefold(x, upper = FALSE)  # jen je třeba upravit druhý argument
## [1] "mixed case 123"
# Character translation - výměna znaku za znak
chartr("iXs", "why", x)     # kde byly symboly i, X, s, tam teď budou w, h, y
## [1] "MwheD cAyE 123"
chartr("a-cX1", "D-Fw9", x) # rozlišuje velká a malá písmena, zvládá i čísla a jiné symboly
## [1] "MiweD FAsE 923"

Spojování stringů:

# "a" + "b"                          # bohužel nefunguje jako v Pythonu
paste("a", "b")                      # automaticky přidává mezeru mezi jednotlivými stringy
## [1] "a b"
paste0("a", "b")                     # bez mezery
## [1] "ab"
paste(c("a", "b"))                   # vektorově funguje po složkách, tedy zde se nic nestane
## [1] "a" "b"
paste(letters[1:2], "c")             # ke každé složce přilepí ten samý
## [1] "a c" "b c"
paste(letters[1:2], letters[3:4])    # po složkách
## [1] "a c" "b d"
paste(letters[1:2], letters[3:4], letters[5:6], sep="...") # sep udává, čím se mají slepit k sobě
## [1] "a...c...e" "b...d...f"
paste(letters[1:6], collapse=" + ")  # z vektoru do jednoho jediného stringu pomocí collapse
## [1] "a + b + c + d + e + f"

Oddělování stringů:

(lslova <- strsplit("Adam šel za Boženou na oběd.", split = " ")) # oddělení mezarami na jednotlivá slova
## [[1]]
## [1] "Adam"    "šel"     "za"      "Boženou" "na"      "oběd."
(lcisla <- strsplit(c("123,456", "0,456", "1,2"), split = ","))   # oddělení čísel před a za desetinnou čárkou
## [[1]]
## [1] "123" "456"
## 
## [[2]]
## [1] "0"   "456"
## 
## [[3]]
## [1] "1" "2"
# Vrací vždy jako list vektorů
(slova <- unlist(lslova))                                         # jednotlivá slova ve vektoru
## [1] "Adam"    "šel"     "za"      "Boženou" "na"      "oběd."
(cisla <- matrix(as.numeric(unlist(lcisla)), nrow = 2))           # 1.ř. jednotky, 2.ř. desetinná místa
##      [,1] [,2] [,3]
## [1,]  123    0    1
## [2,]  456  456    2

Část stringu:

tel <- "+420 123 456 789"
substr(tel, start = 6, stop = nchar(tel))  # jen část tel. čísla po předvolbě
## [1] "123 456 789"
months <- c("January", "February", "March")
substr(months, start = 1, stop = 3)        # jen první tři písmena měsíců
## [1] "Jan" "Feb" "Mar"

Substituce stringů:

chartr(".", ",", "123.456")                             # znak za znak, více ukázek výše
## [1] "123,456"
sub("@", "[at]", "vavraj@karlin.mff.cuni.cz")           # změna za více znaků
## [1] "vavraj[at]karlin.mff.cuni.cz"
sub("mathrm", "mathsf", "\\mathrm{d} x \\mathrm{d} y")  # sub() mění jen PRVNÍ výskyt
## [1] "\\mathsf{d} x \\mathrm{d} y"
gsub("mathrm", "mathsf", "\\mathrm{d} x \\mathrm{d} y") # gsub() mění KAŽDÝ výskyt
## [1] "\\mathsf{d} x \\mathsf{d} y"

Vyhledávání, které stringy obsahují daný pattern:

coef_names <- c("(Intercept)", "age", "gender", "height", "age:gender", "age:height", "gender:height")
(cisla_stringu_s_age <- grep("age", coef_names))   # grep zde vrací čísla stringů obsahujících "age"
## [1] 2 5 6
(obsahuje_age <- grepl("age", coef_names))         # grepl vrací vektor logických hodnot (T/F)
## [1] FALSE  TRUE FALSE FALSE  TRUE  TRUE FALSE
coef_names[cisla_stringu_s_age]                    # obojí se dá použít k výběru těch stringů  
## [1] "age"        "age:gender" "age:height"
coef_names[obsahuje_age]                           # i pomocí T/F se dá vybírat podvektor
## [1] "age"        "age:gender" "age:height"

Regular expressions

Tohle je samo o sobě poměrně velmi obsáhlá kapitola přesahující i Rko. Zde si ukážeme jen nějaké ty nejpoužívanější příkazy či na co si dát pozor. Pro podrobnější návody stačí googlit a naleznete hned několik návodů v angličtině.

Funkce v Rku, které mají argument pattern jako například grep, grepl, sub, gsub, mohou přijímat konkretizovanější specifikace, přes meta charactery:

Ukážeme si to na příkladech:

slova
## [1] "Adam"    "šel"     "za"      "Boženou" "na"      "oběd."
sub(".", "", slova)         # neodstraní nám tečky, jak bychom čekali, ale odstraní hned první znaky
## [1] "dam"    "el"     "a"      "oženou" "a"      "běd."
sub("\\.", "", slova)       # \. slouží jako tečka, ale jelikož je "\" taky meta character, tak je třeba navíc
## [1] "Adam"    "šel"     "za"      "Boženou" "na"      "oběd"
slova[grep("a", slova)]     # slova obsahující "a"
## [1] "Adam" "za"   "na"
slova[grep("^A", slova)]    # slova začínající na "A"
## [1] "Adam"
slova[grep("a$", slova)]    # slova končící na "a"
## [1] "za" "na"
slova[grep("[0-9]", slova)] # slova obsahující číslice
## character(0)
slova[grep("^[a-o]", slova)]# slova začínající na malá písmena z první půlky abecedy
## [1] "na"    "oběd."
slova[grep("^([a-o]|[A-O])", slova)] # slova začínající na písmena z první půlky abecedy (nehledě na velikost)
## [1] "Adam"    "Boženou" "na"      "oběd."

Chceme-li skutečně použít nějaký meta character, musíme před něj položit "\\":

coef_names[grep("^\\(", coef_names)]
## [1] "(Intercept)"
ukazka <- c(paste0("a", 1:5), paste0("a[", 1:5, "]"))
ukazka[grep("[0-2]", ukazka)]
## [1] "a1"   "a2"   "a[1]" "a[2]"
ukazka[grep("\\[[0-2]\\]", ukazka)]
## [1] "a[1]" "a[2]"

Práce se soubory a adresáři

Už víme, jak si nastavit pracovní adresář. Konkrétně právě pracujeme zde:

(WD <- getwd())  # celá cesta k adresáři
## [1] "M:/Vyuka/Rko/cviceni"
dirname(WD)      # jak se k adresáři dostat
## [1] "M:/Vyuka/Rko"
basename(WD)     # jméno tohoto adresáře
## [1] "cviceni"

Podívejme se, co je zde vše uložené za soubory:

dir(getwd())
list.files(getwd())               # jen soubory 
list.files(getwd(), pattern="R$") # jen soubory končící na R
list.dirs(getwd())                # jen adresáře

S adresáři můžeme pracovat následovně:

TXT <- paste0(dirname(WD), "/txt")         # adresář pro textové soubory
# Nepraktické - záleží na tom, co je oddělovač adresářů, zde "/"
# Praktičtěji - file.path doplní oddělovač sám mezi všechny dané stringy
TXT <- file.path(dirname(WD), "txt")       # pospojuje za sebe
dir.exists(TXT)    # Existuje následující adresář?
dir.create(TXT)    # vytvoří adresář, pokud ještě neexistuje
dir.create(TXT)    # podruhé vrátí warning, že už takový adresář existuje

V adresáři můžeme pomocí Rka pracovat se soubory následovně (více help(file.create)):

file.path(TXT, "test.txt")                  # nová cesta k souboru
file.create(file.path(TXT, "test.txt"))     # vytvoření nového souboru 
file.exists(file.path(TXT, "test.txt"))     # ověření existence souboru
file.rename(file.path(TXT, "test.txt"), file.path(TXT, "priklad1.txt"))     # přejmenování souboru
file.copy(file.path(TXT, "priklad1.txt"), file.path(TXT, "priklad2.txt"))   # okopírování souboru
cat("1 + 1 = 2\n", file = file.path(TXT, "priklad1.txt"))                   # připsání do souboru 1
cat("2 + 2 = 4\n", file = file.path(TXT, "priklad2.txt"))                   # připsání do souboru 2
file.append(file.path(TXT, "priklad1.txt"), file.path(TXT, "priklad2.txt")) # připsání z 2 do 1
file.remove(file.path(TXT, "priklad2.txt")) # odstranění souboru

Pokud se nám nechce neustále opakovat, do kterého souboru to vepisujeme, lze použít sink():

sink(file.path(TXT, "priklad1.txt"))  # otevření souboru
{
  print("1+1=2")                      # print nebude fungovat hezky (jako v Console)
  cat("1+2=3\n")                      # zapisujeme ideálně pomocí cat
  cat("1+3=4\n")                      # \n je symbol pro nový řádek 
  cat("1+4=5\n")
  cat("1+5=6\n")
}
sink()                                # uzavření souboru - NEZAPOMENOUT!

K čemu to je dobré? Můžeme si třeba vyrobit vlastní LaTeXové tabulky s čísly přímo z Rka! Změnilo se ti něco na výpočtech? Nevadí! Jednoduše projdi kód na výrobu tabulky znovu jen s novými čísly.

TAB <- file.path(dirname(WD), "tab")  # nový adresář pro tabulky
n <- c(100, 200, 300)
x <- rnorm(length(n))                 # náhodná čísla
y <- rnorm(length(n))                 # je jich tolik, kolik je prvků v n
dir.create(TAB)
sink(file.path(TAB, "jednoducha_tabulka.tex"))  # otevření souboru
{
  cat("\\begin{tabular}{lcc}\n")                # \ slouží opět jako escape character --> třeba zdvojit
  cat("\\hline\n")                              # horizontální linka
  cat("$n$ & Metoda 1 & Metoda 2 \\\\\n")       # nadpis
  cat("\\hline\n")                              # horizontální linka
  for(i in 1:length(n)){
    cat(n[i], "&", round(x[i], digits = 3), "&", round(y[i], digits = 3), "\\\\\n") # jeden řádek tabulky
  }
  cat("\\hline\n")                              # horizontální linka
  cat("\\end{tabular}")                         # konec tabulky
}
sink()                                          # uzavření souboru

Může se stát, že hodnoty v jednom sloupci nejsou se stejným počtem desetinných míst (což je zvykem). Proto by nejprve měly všechny hodnoty x a y spolu projít funkcí format, která to sjednotí.

Teď už jen stačí vložit tento soubor do texového souboru pomocí \input{tab/jednoducha_tabulka.tex} a je to.

Úložky na procvičení

Vyřešte si to sami, aniž byste se dívali do řešení (úplně dole vespod skriptu).

  1. Předpokládejte, že ve vektoru y máte jména vysvětlovaných proměnných (odezev). Ve vektoru x máte seznam vysvětlujících proměnných. Pro každou odezvu vytvořte formuli následujícího tvaru y_1 ~ x_1 + ... + x_n, kde y_1 značí první název odezvy a x_j značí j-tou vysvětlující proměnnou. Poraďte si bez for cyklu.

  2. Uspořádejte si složku pro toto cvičení přehledně tak, aby Vám to vyhovovalo. Například si založte zvláštní adresáře pro

Nadefinujte si také proměnné, kterými se do těchto adresářů dostanete.

  1. Máte k dispozici dva stringy, ve kterých jsou čárkami oddělené e-mailové adresy uživatelů (vytvořte si sami, třeba náhodně). Přetvořte to ve dva vektory e-mailových adres. Utvořte opět dlouhé stringy e-mailových adres oddělených čárkami, které obsahují průnik, sjednocení (unikátní) a rozdíly množin adres.

  2. Vytvořte LaTeXový kód pro následující tabulku a uložte jej do tabulkového adresáře pod “tabulka.tex”. Začněte s hrubým nástinem a postupně přidávejte na detailech:

Pro jednoduchost předpokládejte, že čísla máte daná v náhodně vygenerovaných maticích X a Y, a to pro každé \(K\) zvlášť:

X <- Y <- list()
Ks <- c(2, 3)
scenare <- LETTERS[1:3]
ns <- c(50,100,200,500,1000)
for(k in Ks){
  X[[k]] <- Y[[k]] <- matrix(nrow = length(ns)*length(scenare), ncol = 2)
  i <- 0
  for(scenar in scenare){
    for(n in ns){
      i <- i + 1
      smpl <- rnorm(n, mean = k, sd = which(scenar == LETTERS))
      X[[k]][i,] <- c(mean(smpl), sd(smpl))
      smpl <- k + rt(n, df = 5+which(scenar == LETTERS))
      Y[[k]][i,] <- c(mean(smpl), sd(smpl))
    }
  }
  colnames(X[[k]]) <- colnames(Y[[k]]) <- c("mean", "sd")
}
X
Y