Uložení lineárního textu. Funkce cat()

Funkce cat umí zpracovat tzv. atomické vektory a řetězce. Atomický vektor je takový, který u sebe nemá žádné atributy kromě jmen elementů. Funkce cat neumí zpracovat faktory, vektory s atributy jinými než jména (names) , seznamy a tabulky.

Nejdůležitější parametry funkce cat() pro ukládání souborů

  • ... - objekt, který chceme uložit
  • file - název souboru, kam objekt chceme uložit, včetně jeho umístění, pokud nemá vzniknout přímo v pracovním adresáři. Tím ten soubor zároveň vytvoříme, pokud na daném místě už neexistuje.
  • sep - čím se mají oddělit jednotlivé elementy? Default je mezera.
  • append - nabývá hodnot TRUE nebo FALSE. Když je FALSE, což je default, nebere v úvahu existenci předchozích verzí daného souboru. Pokud soubor daného jména na daném místě existoval a něco v něm bylo, všechno se to ztratí, protože se to přepíše tím novým uložením. Když je však tento parametr TRUE, funkce nejdřív zkontroluje, zda se na daném místě nenachází předchozí verze daného souboru, a když ano, nový obsah připojí za ten starý. Pokud soubor toho jména neexistuje, tak ho vytvoří. Toto nastavení se hodí například v cyklech, kdy výstup ukládáme postupně.
muj_maly_text <- c("R version 3.2.4 (2016-03-10) -- \"Very Secure Dishes\"", "Copyright (C) 2016 The R Foundation for Statistical Computing", "Platform: i386-w64-mingw32/i386 (32-bit)")
cat(muj_maly_text, file = "results/muj_maly_text.txt", sep = "\n")
list.files(pattern = "results/muj_maly_te")
## character(0)

Uložením souboru nevzniká žádný další datový objekt, nepoužíváme žádné přiřazení! Ukázka:

a <- cat(muj_maly_text, file = "results/muj_maly_text.txt")
a
## NULL

Ještě si předvedeme postupné zapisování do souboru. Do souboru abeceda.txt postupně zapíšeme písmena. Úlohu umístíme do for-cyklu, aby byla zřejmá ta postupnost, a cyklus uprostřed abecedy skončíme, abychom se do souboru podívali. Soubor zatím nikde neexistuje, vytvoří se v prvním průchodu cyklem uložením první hodnoty. Další hodnoty se budou připisovat.

for (i in 1:(length(letters)/2)) {
  cat(letters[i], file = "results/abeceda.txt", append = TRUE)
}

Pro změnu si soubor načteme po řádcích:

readLines("results/abeceda.txt")
## Warning in readLines("results/abeceda.txt"): incomplete final line found on
## 'results/abeceda.txt'
## [1] "abcdefghijklm"

Písmena se řadí za sebou bez mezer. Jak to, vlastně, když defaultní separátor u funkce cat() je mezera? Protože mezerami by byly od sebe oddělené elementy, které se zapisují najednou. To se tady nijak neprojeví, protože zapisujeme vždy jenom jeden element. Separátor se ale nikdy nevloží na konec sekvence. Kdybychom chtěli zapisovat každé písmeno na zvláštní řádek, museli bychom to udělat takhle:

for (i in ((length(letters)/2) + 1):length(letters)) {
  cat(letters[i], "\n", file = "results/abeceda.txt", append = TRUE)
}

readLines("results/abeceda.txt")
##  [1] "abcdefghijklmn " "o "              "p "             
##  [4] "q "              "r "              "s "             
##  [7] "t "              "u "              "v "             
## [10] "w "              "x "              "y "             
## [13] "z "

V textovém editoru bude soubor vypadat takhle:

  abcdefghijklmn 
  o 
  p 
  q 
  r 
  s 
  t 
  u 
  v 
  w 
  x 
  y 
  z 

Z toho plyne ještě jeden postřeh: funkce cat() může mít libovolné množství argumentů typu “to, co se má uložit”. Je to všechno, co je v uvozovkách a není to označeno parametrem file. Název souboru, do kterého se to má uložit, musí být vždy označen parametrem file, jinak se vše, co je v uvozovkách, včetně toho názvu souboru, vytiskne prostě na obrazovku. Takhle:

for (i in ((length(letters)/2) + 1):length(letters)) {
  cat(letters[i], "\n", "results/abeceda.txt", append = TRUE)
}
## n 
##  results/abeceda.txto 
##  results/abeceda.txtp 
##  results/abeceda.txtq 
##  results/abeceda.txtr 
##  results/abeceda.txts 
##  results/abeceda.txtt 
##  results/abeceda.txtu 
##  results/abeceda.txtv 
##  results/abeceda.txtw 
##  results/abeceda.txtx 
##  results/abeceda.txty 
##  results/abeceda.txtz 
##  results/abeceda.txt

Hodnota parametru append je irelevantní a skript na žádné z nich nespadne.

Pohled do souboru a načtení s různými separátory

Prohlédneme si vzniklý soubor v textovém editoru:

  R version 3.2.4 (2016-03-10) -- "Very Secure Dishes"
  Copyright (C) 2016 The R Foundation for Statistical Computing
  Platform: i386-w64-mingw32/i386 (32-bit)

Díky separátoru \n se každý element zapsal na zvláštní řádek.

Prohlédneme si obsah souboru ještě opětným načtením do R. Na rozdíl od zapisování do souboru, při čtení souboru si jeho obsah do proměnné uložit musíme.

pohled_do_souboru <- scan("results/muj_maly_text.txt", what = "character", sep = " ")
pohled_do_souboru
##  [1] "R"                     "version"              
##  [3] "3.2.4"                 "(2016-03-10)"         
##  [5] "--"                    "Very Secure Dishes"   
##  [7] "Copyright"             "(C)"                  
##  [9] "2016"                  "The"                  
## [11] "R"                     "Foundation"           
## [13] "for"                   "Statistical"          
## [15] "Computing"             "Platform:"            
## [17] "i386-w64-mingw32/i386" "(32-bit)"

Schválně jsme si ho načetli s jiným separátorem, abychom si názorně ukázali, že soubor a obsah proměnné se každý řídí svými vlastními parametry. Při načtení se nedá nijak říct: “Použij stejný separátor, jaký byl použit při zapisování té původní proměnné do souboru”. Tato informace je zcela irelevantní a nikde se neukládá!

Teď obsah souboru přepíšeme. Mezi souborem a proměnnou, která do něj byla zapsaná, nevznikl žádný vztah. Obsah souboru můžeme klidně přepsat obsahem jiné proměnné.

novy_maly_text <- c("Ema má mísu.")

cat(novy_maly_text, file = "results/muj_maly_text.txt")

list.files(pattern = "results/muj_maly_te")
## character(0)

Soubor toho jména na daném místě stále existuje, ale uvnitř má něco jiného:

Ema má mísu.
pohled_do_souboru <- scan("results/muj_maly_text.txt", what = "character", sep = " ")
pohled_do_souboru
## [1] "Ema"   "má"    "mísu."

Uložení tabulky. Funkce write.table() a její odvozeniny

Funkcí na zapisování všeho možného mnoha různými způsoby je dlouhá řada - zkuste našeptávači v konzoli podstrčit ?write (a zmáčkněte tabelátor). My si vystačíme se dvěma, nejvýš čtyřmi - jednou, případně třemi, pro tabulky, a jednou pro texty.

Tabulky se ukládají funkcí write.table, ke které ještě existují odvozeniny s přednastavenými hodnotami na míru americkému, respektive evropskému, formátu souborů .csv: funkce write.csv() a write.csv2(). Podrobněji se podíváme jenom na write.table(), protože ty druhé dvě vznikly vhodným přednastavením defaultní hodnoty některých jejích parametrů, a když porozumíme write.table(), nebude v nich nic nového. Chování funkce write.table() si předvedeme na datasetu iris. Uložíme si ho do proměnné a.

a <- iris
head(a)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa

Co se stane, když tabulku uložíme s defaultními hodnotami parametrů?

write.table(a, file = "results/iris.csv")

Otevřeme ji v textovém editoru a vypadá takto:

    "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species"
    "1" 5.1 3.5 1.4 0.2 "setosa"
    "2" 4.9 3 1.4 0.2 "setosa"
    "3" 4.7 3.2 1.3 0.2 "setosa"
    "4" 4.6 3.1 1.5 0.2 "setosa"

Parametr row.names je defaultně nastaven na TRUE. Vyprodukuje tedy čísla řádků jako jejich názvy. Pokud se mu místo TRUE dá znakový vektor odpovídající délky, pojmenuje řádky podle elementů toho vektoru. Typicky se jmény řádků může stát některý ze sloupců tabulky.

Zrovna tak je na defaultní hodnotu TRUE nastaven parametr col.names. Při tomto nastavení uloží názvy sloupců. Kdyby tabulka žádné sloupce neměla, v souboru by se nastavením parametru col.names na TRUE žádné nevytvořily. Vytvoříme si tabulku iris bez záhlaví a uložíme ji do proměnné b.

b <- a
colnames(b) <- NULL
head(b)
##    NA  NA  NA  NA     NA
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa
## 6 5.4 3.9 1.7 0.4 setosa

Tuto proměnnou uložíme do souboru s col.names = TRUE:

write.table(b, "results/iris_bez_zahlavi.csv", col.names = TRUE)

A výstup vypadá takhle:

"1" 5.1 3.5 1.4 0.2 "setosa"
"2" 4.9 3 1.4 0.2 "setosa"
"3" 4.7 3.2 1.3 0.2 "setosa"
"4" 4.6 3.1 1.5 0.2 "setosa"
"5" 5 3.6 1.4 0.2 "setosa"

Aby tabulka bez záhlaví nějaké záhlaví dostala při zápisu do souboru, musely by se názvy sloupců doplnit jako znakový vektor do parametru col.names. Pozor, je lepší si vždy vypnout čísla řádků, protože jinak se v tabulkových procesorech posunou názvy sloupců doleva. Zároveň nepůjde uložit o jeden sloupec víc a v prvním mít název třeba “čísla řádků”, takže se tabulka rozbije.

write.table(b, "results/iris_bez_zahlavi.csv", col.names = c("prvnicisla", "druha_cisla", "treticisla", "ctvrtacisla", "kyticky"))

Takhle:

"prvnicisla" "druha_cisla" "treticisla" "ctvrtacisla" "kyticky"
"1" 5.1 3.5 1.4 0.2 "setosa"
"2" 4.9 3 1.4 0.2 "setosa"
"3" 4.7 3.2 1.3 0.2 "setosa"

Jako "kyticky" totiž bude ve skutečnosti pojmenován sloupec obsahující hodnoty 0.2, 0.2, 0.2 a sloupec obsahující hodnoty "setosa" zůstane nepojmenovaný.

Zajímavý je taky parametr quote. Defaultně je zapnutý a způsobí, že všechny znakové elementy budou obklopeny dvojitými uvozovkami. Představme si, že ve sloupci schováváme korpusové konkordance, kde se to apostrofy a uvozovkami jen hemží. Bude nám takovýhle parametr ke škodě, nebo k užitku, až budeme tabulku zase načítat ze souboru do nové proměnné?

Nahradíme slovo setosa v prvních pěti řádcích textem s uvozovkami. Samozřejmě je musíme opatřit zpětným lomítkem.

d <- a
d$Species <- as.character(d$Species)
d$Species[which(d$Species == "setosa")][1:5] <- "Manka zpívala \"tralala\""
head(d)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1          5.1         3.5          1.4         0.2
## 2          4.9         3.0          1.4         0.2
## 3          4.7         3.2          1.3         0.2
## 4          4.6         3.1          1.5         0.2
## 5          5.0         3.6          1.4         0.2
## 6          5.4         3.9          1.7         0.4
##                   Species
## 1 Manka zpívala "tralala"
## 2 Manka zpívala "tralala"
## 3 Manka zpívala "tralala"
## 4 Manka zpívala "tralala"
## 5 Manka zpívala "tralala"
## 6                  setosa

Teď to zkusíme uložit s uvozovkami:

write.table(d, "results/uvozovky.csv", quote = TRUE, row.names = FALSE, sep = ";", col.names = TRUE)

Zkusíme načíst zpátky s vnímáním uvozovek:

f <- read.table("results/uvozovky.csv", sep = ";", quote = "\"", header = TRUE)
str(f)
## 'data.frame':    150 obs. of  5 variables:
##  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : Factor w/ 4 levels "Manka zpívala \\tralala\\",..: 1 1 1 1 1 2 2 2 2 2 ...
head(f)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1          5.1         3.5          1.4         0.2
## 2          4.9         3.0          1.4         0.2
## 3          4.7         3.2          1.3         0.2
## 4          4.6         3.1          1.5         0.2
## 5          5.0         3.6          1.4         0.2
## 6          5.4         3.9          1.7         0.4
##                     Species
## 1 Manka zpívala \\tralala\\
## 2 Manka zpívala \\tralala\\
## 3 Manka zpívala \\tralala\\
## 4 Manka zpívala \\tralala\\
## 5 Manka zpívala \\tralala\\
## 6                    setosa

a bez vnímání uvozovek:

g <- read.table("results/uvozovky.csv", sep = ";", quote = "", header = TRUE)
str(g)
## 'data.frame':    150 obs. of  5 variables:
##  $ X.Sepal.Length.: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ X.Sepal.Width. : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ X.Petal.Length.: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ X.Petal.Width. : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ X.Species.     : Factor w/ 4 levels "\"Manka zpívala \\\"tralala\\\"\"",..: 1 1 1 1 1 2 2 2 2 2 ...
head(g)
##   X.Sepal.Length. X.Sepal.Width. X.Petal.Length. X.Petal.Width.
## 1             5.1            3.5             1.4            0.2
## 2             4.9            3.0             1.4            0.2
## 3             4.7            3.2             1.3            0.2
## 4             4.6            3.1             1.5            0.2
## 5             5.0            3.6             1.4            0.2
## 6             5.4            3.9             1.7            0.4
##                      X.Species.
## 1 "Manka zpívala \\"tralala\\""
## 2 "Manka zpívala \\"tralala\\""
## 3 "Manka zpívala \\"tralala\\""
## 4 "Manka zpívala \\"tralala\\""
## 5 "Manka zpívala \\"tralala\\""
## 6                      "setosa"

Teď totéž provedeme s quote = FALSE při zapisování do souboru

write.table(d, "results/bez_uvozovky.csv", quote = FALSE, row.names = FALSE, sep = ";", col.names = TRUE)

Zkusíme načíst zpátky s vnímáním uvozovek:

h <- read.table("results/bez_uvozovky.csv", sep = ";", quote = "\"", header = TRUE)
str(h)
## 'data.frame':    150 obs. of  5 variables:
##  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : Factor w/ 4 levels "Manka zpívala tralala",..: 1 1 1 1 1 2 2 2 2 2 ...
head(h)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width               Species
## 1          5.1         3.5          1.4         0.2 Manka zpívala tralala
## 2          4.9         3.0          1.4         0.2 Manka zpívala tralala
## 3          4.7         3.2          1.3         0.2 Manka zpívala tralala
## 4          4.6         3.1          1.5         0.2 Manka zpívala tralala
## 5          5.0         3.6          1.4         0.2 Manka zpívala tralala
## 6          5.4         3.9          1.7         0.4                setosa

a bez vnímání uvozovek:

j <- read.table("results/bez_uvozovky.csv", sep = ";", quote = "", header = TRUE)
str(j)
## 'data.frame':    150 obs. of  5 variables:
##  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : Factor w/ 4 levels "Manka zpívala \"tralala\"",..: 1 1 1 1 1 2 2 2 2 2 ...
head(j)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1          5.1         3.5          1.4         0.2
## 2          4.9         3.0          1.4         0.2
## 3          4.7         3.2          1.3         0.2
## 4          4.6         3.1          1.5         0.2
## 5          5.0         3.6          1.4         0.2
## 6          5.4         3.9          1.7         0.4
##                   Species
## 1 Manka zpívala "tralala"
## 2 Manka zpívala "tralala"
## 3 Manka zpívala "tralala"
## 4 Manka zpívala "tralala"
## 5 Manka zpívala "tralala"
## 6                  setosa

Pro textová data vychází jako přehlednější zápis do souboru bez uvozovek obklopujících textové elementy a vypnutí vnímání uvozovek jako metatextu. Přesto se jistě setkáte s případy, kdy textová data nepůjdou načíst kvůli problémům s čtením uvozovek. Často pomůže vyzkoušet různé varianty, včetně apostrofů (když máte data v angličtině).

Tisk výstupu do konzole

Potřebujete si tisknout na obrazovku průběžná hlášení, co váš skript právě dělá? Například kolikátý průchod cyklem právě skončil? Na to se dobře používají funkce cat() a print(). Nám již známá funkce cat() má tu výhodu, že se do ní dá napsat celá šablona kombinovaná z řetězců a proměnných, např. Právě jsem skončil x-tý průchod., kde se za x pokaždé dosadí aktuální hodnota tak pojmenované proměnné. Funkce print() musí mít na vstupu právě jeden řetězec. Samozřejmě se dá napsat něco jako print(paste("Právě jsem dokončil ", x, "tý průchod.")). Pomocí funkce print() nejde zapisovat do souboru, je tedy omezenější. V nápovědě se píše: “cat() performs much less conversion than print()”. My je asi pro tisk na obrazovku můžeme považovat za vzájemně zaměnitelné.