Disperzní graf výskytu slova whale po kapitolách

Náš implementační cíl

Pozor, tato lekce bude poněkud košatější na několika rovinách. Jednak je postup trochu delší, jednak je skript napěchován novými programovacími koncepty. Jockers nám věří… :-) . Zadání celého úkolu je vytvořit disperzní graf výskytů slova whale po kapitolách, bez názvů kapitol. Výsledkem má být disperzní graf, jaký jsme už vyráběli pro celý román najednou, ale tentokrát má každá jednotka na ose X reprezentovat jednu kapitolu (a ne jedno slovo, jako tomu bylo v předchozí úloze) a každá čárka v grafu znázorňovat nějaký nenulový počet výskytů slova whale v odpovídající kapitole. Místo binární proměnné (tj. “dané slovo je/není whale”) máme kvantitativní proměnnou (kolikrát je v dané kapitole slov, která jsou whale?). Můžeme počítat buď s absolutními počty výskytů, nebo relativními (kolik procent slov z každé kapitoly je whale?)

Předpokladem pro zdar je frekvenční slovník pro každou kapitolu. To byl náš cíl 24.3. ve škole a zbytek za domácí úkol.

Nové programovací koncepty

Jistě si vzpomínáte, že jsme si tuto úlohu už implementovali s použitím funkce strapply z balíčku gsubfn. Teď budeme postupovat podle Jockersovy učebnice. Skript je asi schválně trochu zdlouhavý, protože se na něm předvádí hned několik velmi důležitých programovacích konceptů. For-cyklus (for loop) a podmínka (condition) jsou univerzální, mají je asi všechny programovací jazyky. Funkce z rodiny apply jsou asi specifické pro R a do.call taky. My potkáme lapply, (což se dá do lidské řeči přeložit přibližně jako apply a function to my list) a použijeme ji pro uplatnění námi vybrané funkce na každém prku seznamu (list) najednou. To bude pokrok, protože seznam jsme si zatím vždy museli převést na vektor pomocí funkce unlist, abychom ho mohli použít dál. Kdyby v něm náhodou bylo něco složitějšího než jednoelementový vektor, byla by to naše konečná.

Jak jsme úlohu implementovali jednodušeji s pomocí funce strapply

Jen pro připomenutí. Skript je zakomentován, aby se nespouštěl.

# load("novel.lower.v.RData")
# moby.kap.l  <- strsplit(novel.lower.v, "chapter \\d+") #roztrháme si ten dlouhatánský řetězec po kapitolách. Zmizí kapitoly a jejich čísla
# moby.kap.v <- unlist(moby.kap.l) #ani jedno nezobrazovat, trvá to dlouho a RStudio na tom někdy padá
# 
# moby.kap.v <- moby.kap.v[moby.kap.v != ""] #strsplit zase nadělalo prázdné elementy, kde něco odstranilo, zbavme se jich
# 
# if (require(gsubfn) == FALSE) {
#   install.packages("gsubfn")
# }
# library(gsubfn)
# nalezy.strapply <- strapply(moby.kap.v,"whale")
# nalezy.strapply[1:10]
# (pocty <- sapply(nalezy.strapply, length))
# plot(pocty, type = "h")

A teď na to půjdeme úplně jinak a z tohoto skriptu nepoužijeme vůbec nic!

Jak jsme vytvářeli frekvenční seznam slov pro celý román najednou

Tohle si musíme připomenout taky, protože stejný postup teď použijeme v jedné kapitole po druhé v rámci našeho prvního for-cyklu. V jednom z předchozích programovacích cvičení jsme vytvořili frekvenční slovník pro slova v celém románu. Před románem i za ním byla metadata, která jsme museli napřed odstranit. Vzali jsme celý román po řádcích a určili pozici prvního a posledního řádku vlastního románu. Od počátečního do konečného řádku jsme si řádky spojili, takže jsme chvíli měli vektor s jedním dlouhatánským elementem. Ten element jsme pomocí funkce strsplit roztrhali na jednotlivá slova. Pravda, z trhání nám tam zbyl nějaký šum po odstraněných interpunkčních znaménkách a mezerách, spousta prázdných elementů. Těch jsme se ale v jednom kroku zbavili tak, že jsme si stávající proměnnou přepsali jejím vlastním výběrem, ve kterém ten šum už nefiguroval.

Kód pro připomenutí:

text.v <- scan("data/plainText/melville.txt", what = "character", sep = "\n")
filename <- file.path("C:", "Seafile", "Silvie_R", "R_VYUKA_2016", "ERKO", "data", "plainText", "melville.txt")
text.v <- scan(filename, what = "character", sep = "\n") # načetli jsme celý dokument po řádcích,  
#ale pozor, před románem i po románu byl text - metadata, který jsme nechtěli.
start.v <- which(text.v == "CHAPTER 1. Loomings.")  # první řádek románu
end.v <- which(text.v == "orphan.") # poslední řádek románu
start.metadata.v <- text.v[1:start.v - 1]  # metadata před románem: řádek 1 
#až řádek před řádkem se začátkem románu
end.metadata.v <- text.v[(end.v + 1):length(text.v)] # metadata za románem: 
#řádek po konci románu (tj. číslo řádku s koncem románu plus 1 až poslední řádek dokumentu)
#metadata.v <- c(start.metadata.v, end.metadata.v) #tohle nás teď nezajímá
novel.lines.v <-  text.v[start.v:end.v] # tohle je náš román po řádcích
novel.v <- paste(novel.lines.v, collapse = " ") # tady jsme z něj udělali jeden element 
novel.lower.v <- tolower(novel.v) # tady jsme celý text převedli na malá písmena
moby.words.l <- strsplit(novel.lower.v, "\\W") # tady jsme si z toho jednoho 
#elementu s celým textem udělali vektor se slovy jako elementy 
moby.word.v <- unlist(moby.words.l) # funkce strsplit vrací seznam,  
#ale my chceme vektor, použili jsme funkci unlist.
not.blanks.v  <-  which(moby.word.v != "") #po znacích \\W nám zbyly prázdné elementy,
#zbavíme se jich
moby.word.v <-  moby.word.v[not.blanks.v] # A už je tam nemáme.

Malinká odbočka, jak se má tabulka table k matici (matrix), data.frame a pojmenovanému vektoru

Jakmile jsme získali vektor plný jednotlivých tokenů jako elementů, stačilo zavolat funkci table. Získali jsme tabulku typu table (není to matice - matrix ani tabulka data.frame), která je vlastně vektorem počtů výskytů a každý jeho element má nálepku (jméno, name), které je stejné jako type (unikátní slovo), jehož počet to číslo v elementu vyjadřuje. Pojmenovaný vektor může být jakéhokoli typu - numerický, znakový i logický. Jména jsou vždy znakovým vektorem. Pojmenovaný vektor si můžeme taky představit jako vektor, který má na sobě nalepený ještě jeden vektor, který se “jmenuje” names(název našeho vektoru) a má stejný počet elementů jako ten náš vektor, a kdykoli něco provedeme tomu našemu vektoru (něco smažeme, přidáme nebo přehodíme), totéž se stane i tomu vektoru jmen. Jakmile si ho ale uložíme do nějaké proměnné, bude si žít vlastním životem a změny na našem vektoru se ho přestanou týkat. K aktuální podobě jmen v pojmenovaném vektoru se dá přistoupit pouze jako k names(název našeho vektoru). Když je nějaký vektor pojmenovaný, tj. jeho names() není prázdný vektor, můžeme svůj vektor prohledávat pomocí těch jmen. Vidíte rozdíl?

# Nepojmenovaný vektor
a <- c("buchty","salám","chipsy")
# Chci-li se dozvědět, který element obsahuje "chipsy", musím vybírat testem:
which(a == "chipsy")
## [1] 3
# Tento vektor nemá svoje elementy pojmenované:
names(a)
## NULL

Pojmenovaný vektor z nepojmenovaného vektoru udělám tak, že dodám jména elementů. Oba vektory musí být stejně dlouhé, ale jména nemusí být unikátní. Tím se tato struktura liší např. od dictionary v Pythonu, i když je její využití podobné.

names(a) <- c("Máňa", "Pepa", "Máňa")

Pojmenovaný vektor mi dává možnost smysluplně použít i otázku: “Co má ráda Máňa?”, aniž bych znala odpověď dopředu:

a["Máňa"]
##     Máňa 
## "buchty"

Srv.:

names(a) <- NULL
a[a == "buchty"]
## [1] "buchty"

V případě frekvenčního slovníku mi jmenný vektor dává možnost se přímočaře zeptat: “Kolikrát se v románu vyskytuje slovo whale”?

load("moby.freqs.t.RData")
moby.freqs.t["whale"]
## whale 
##  1150
str(moby.freqs.t) # vida, opravdu se skládá z vektoru celých čísel (int) a má atribut, který se jmenuje dimnames a je to znakový vektor stejné délky!!!
##  'table' int [1:16872(1d)] 1 1 20 2 3 1 1 1 1 1 ...
##  - attr(*, "dimnames")=List of 1
##   ..$ moby.word.v: chr [1:16872] "_ile_" "_you_" "000" "1" ...

KONEC ODBOČKY O TABULCE TABLE

To bylo pro připomenutí, co je frekvenční tabulka a jak se v ní orientovat. V první fázi našeho programu použijeme for-cyklus k vytvoření frekvenčního slovníku tohoto formátu pro každou kapitolu.

Odbočka na vysvětlení cyklu

Cyklus se dá do lidské řeči přeložit takto: “Postav si věci do fronty, očísluj si je, z fronty jednu věc odeber a udělej s ní to, co ti říká skript uvnitř složených závorek. Až to provedeš, vezmi další věc z fronty a udělej s ní totéž. To se bude opakovat, kolikrát nařídíš. Až bude splněn počet zadaných opakování, skript se posune dál a budou se provádět příkazy následující za cyklem.”

Cyklus se skládá z for, kulaté závorky s in a páru složených závorek. Uvnitř kulaté závorky se určuje, které věci se postaví do fronty na zpracování. Proměnná pro “jednu takovou věc” se jmenuje i jako integer, ale to je jenom konvence, dá se změnit (např. (element_k_uprave in apod.). Po tom in musí následovat číselný vektor. Úplně nejtypičtější je, že chceme něco provést s každým elementem vektoru (třeba), takže chceme pořadí čísel účastníků ve frontě od jedničky do pořadového čísla posledního elementu vektoru: 1:length(nas_vektor_k_upraveni). V následujícím příkladu si vytiskneme řádky s názvy kapitol románu od první do desáté.

chap.positions.v <- grep("^CHAPTER \\d", novel.lines.v)
nazvy.kapitol <- novel.lines.v[chap.positions.v] #jen pro ilustraci
for (i in 1:10) {
  print(nazvy.kapitol[i])
}
## [1] "CHAPTER 1. Loomings."
## [1] "CHAPTER 2. The Carpet-Bag."
## [1] "CHAPTER 3. The Spouter-Inn."
## [1] "CHAPTER 4. The Counterpane."
## [1] "CHAPTER 5. Breakfast."
## [1] "CHAPTER 6. The Street."
## [1] "CHAPTER 7. The Chapel."
## [1] "CHAPTER 8. The Pulpit."
## [1] "CHAPTER 9. The Sermon."
## [1] "CHAPTER 10. A Bosom Friend."

Všimněte si, že i se objevuje ve skriptu v těch složených závorkách, kde se píše, co se má s každým účastníkem fronty, tj. tady s řádkem nesoucí název kapitoly románu, udělat, tj. že se má vytisknout. Mašinérie si za i v prvním průchodu dosadí jedničku, v druhém dvojku a tak dál až do desítky. Pak se začne dívat, co se má dělat po cyklu, a skript pokračuje dál. Tak se vytisknou všechny řádky jeden po druhém (a ano, máte pravdu, v R by bývalo úplně stačilo napsat print(nazvy.kapitol[1:10]). Však se v R na takovéhle jednoduché věci for-cyklus nepoužívá a je tu jako maximálně srozumitelný zástupce libovolně složitého příkazového kódu! Důležité je, že se v kódu uplatní to i. Kdyby tam nebylo, příkaz by znamenal “vytiskni desetkrát celý vektor nazvy.kapitol”. To je opravdu velký rozdíl!!!

A v každém případě je nutné zdůraznit, že to, co je v kulaté závorce, má být sekvence čísel, která z nějakého objektu vybírá elementy podle jejich pořadí, a NEMAJÍ TO BÝT TY PRVKY SAMOTNÉ!!!

Ale jak si výsledek uložím? Chci názvy kapitol velkým písmem. Schválně si ten kód odkomentujte a spusťte. Nedopadne to dobře na několik způsobů.

# for (i in 1:length(nazvy.kapitol)) {
#   if (i < length(nazvy.kapitol)){
#     seznam.kapitol<- toupper(nazvy.kapitol[i])
#     
#   }
# }

#seznam.kapitol 

Ajajaj, tak to jsme nechtěli, obsah proměnné se nám pořád dokolečka přepisoval! Jak způsobíme, že se tam výsledky budou přidávat? Zkusme toto:

# for (i in 1:length(nazvy.kapitol)) {
#   if (i < length(nazvy.kapitol)) {
#     seznam.kapitol.02 <- c(seznam.kapitol.02, toupper(nazvy.kapitol[i]))
#     
#   }
# }
# 
# seznam.kapitol.02

No tak to jsme taky nechtěli. Co se stalo? Proměnná se nemůže definovat sama sebou. Napřed ji musíme definovat jako prázdnou.

# for (i in 1:length(nazvy.kapitol)) {
#   if (i < length(nazvy.kapitol)){
#     seznam.kapitol.03 <- character()
#     seznam.kapitol.03 <- c(seznam.kapitol.03, toupper(nazvy.kapitol[i]))
#     
#   }
# }
# 
# seznam.kapitol.03

A zase tam máme jenom poslední výsledek. Proč?

Ten prázdný kontejner na výsledky se musí objevit už PŘED cyklem!

seznam.kapitol.04 <- character()

for (i in 1:length(nazvy.kapitol)) {
  if (i < length(nazvy.kapitol)) {
    
    seznam.kapitol.04 <- c(seznam.kapitol.04, toupper(nazvy.kapitol[i]))
    
  }
}
head(seznam.kapitol.04, 4)
## [1] "CHAPTER 1. LOOMINGS."        "CHAPTER 2. THE CARPET-BAG." 
## [3] "CHAPTER 3. THE SPOUTER-INN." "CHAPTER 4. THE COUNTERPANE."
tail(seznam.kapitol.04, 4)
## [1] "CHAPTER 131. THE PEQUOD MEETS THE DELIGHT."
## [2] "CHAPTER 132. THE SYMPHONY."                
## [3] "CHAPTER 133. THE CHASE--FIRST DAY."        
## [4] "CHAPTER 134. THE CHASE--SECOND DAY."

Takže k šabloně pro for-cyklus patří ještě i to, že pro průběžné ukládání výsledků si musíme vytvořit prázdnou proměnnou PŘED CYKLEM a v cyklu si ji přepisovat a při každém přepsání k ní přidat výsledek z toho daného průchodu cyklem. A to je vše, co je potřeba o for-cyklu vědět obecně.

Vracíme se zpět k našemu úkolu. Uvnitř bude ještě malá odbočka s vysvětlením podmínky (condition).

Vytvoření frekvenčních seznamů všech slov pro každou kapitolu

Tohle jsme dělali 24.3.

rm(list = ls()) #vyčisti prac. prostředí. Kdyby to nešlo přes Knit, tak tento řádek zakomentujte a pracovní prostředí předem vyčistěte ručně ("smetákem")
text.v <- scan("data/plainText/melville.txt", what = "character", sep = "\n")
start.v <- which(text.v == "CHAPTER 1. Loomings.")
end.v <- which(text.v == "orphan.")
novel.lines.v <-  text.v[start.v:end.v]

Teď máme řádky románu bez metadat. Budeme chtít identifikovat řádky s nadpisy kapitol. Texty kapitol si poukládáme do seznamu.

chap.positions.v <- grep("^CHAPTER \\d", novel.lines.v) 

Klidně jsme použili grep, i když hledá jenom první výskyt hledaného řetězce, protože prohledává každý řádek a my se můžeme domnívat, že nadpis kapitoly je na jednom řádku nejvýše jednou!

Abychom vytvořili frekvenční slovník pro každou kapitolu (ignorujeme texty v nadpisech kapitol), budeme ve for-cyklu zpracovávat po jednom všechny segmenty od řádku následujícího po řádku se začátkem kapitoly (ty máme zjištěné ve vektoru chap.positions.v) do řádku o jedničku menšího, než je číslo řádku s následujícím nadpisem kapitoly. To bude fungovat až do předposlední kapitoly, ale konec poslední kapitoly se takhle nenajde. Pomůžeme si tím, že k textu přidáme řádek s nápisem END a číslo jeho pozice přidáme do vektoru s pozicemi začátků kapitol. Tohle je jen jeden z možných způsobů implementace, s trochou praxe si něco podobného sami zvládnete implementovat individuálně jinak. M. Jockers tedy přidal řádek nakonec, aby mohl co nejvíc recyklovat svůj skript o frekv. slovníku celého románu najednou, kde obsah románu vypárával z metadat.

novel.lines.v <- c(novel.lines.v, "END")   # ady přidal řádek k románu
last.position.v <- length(novel.lines.v)  # tady si zjistil číslo jeho pozice
chap.positions.v <- c(chap.positions.v , last.position.v) # tady TO ČÍSLO přidal 
#k ostatním číslům s nadpisy kapitol

A teď pozor, co chceme udělat: z vektoru s texty řádků chceme pokaždé v cyklu vybrat řádek s indexem o jedničku vyšším než je číslo, které je na řadě ve vektoru chap.positions jako začátek textu kapitoly a řádek s indexem o jedničku nižším, než má ten DALŠÍ číselný element ve vektoru chap.positions. Pro ilustraci: kdyby se nadpisy nacházely na řádcích 1 a 100, ve vektoru chap.positions.v by na prvním a druhým elementem bylo 1 a 100. Pro zpracování první kapitoly do frekvenčního slovníku by se tedy použily řádky novel.lines.v[2:99], což by se z pohledu vektoru chap.positions.v vyjádřilo takto:

novel.lines.v[chap.positions.v[1] + 1], čili novel.lines.v[(1 + 1)]by byl začátek a

novel.lines.v[chap.positions.v[2] - 1], čili novel.lines.v[(100 - 1)] by byl konec první kapitoly.

Takto by vypadal ten celý interval: novel.lines.v[((chap.positions.v[i]) + 1 ): ((chap.positions.v[i+1]) - 1)].

Pozor na závorkování! Jde o číslo o jedničku vyšší,respektive nižší než číslo, které je na i-té, respektive i-plus-první, tedy následující po i, pozici. Ještě jinak: index (pořadové číslo) nadpisu na začátku naší sledované kapitoly je výchozí, tedy i. Index konce naší sledované kapitoly je o jednu vyšší než [i], tedy [i + 1]. Když i bude 32, i + 1 bude 33.

Kromě toho se děje ještě jedna věc: pod indexem i se skrývá nějaké číslo, protože indexuji číselný vektor s čísly řádků, na kterých jsou nadpisy kapitol. To číslo, třeba 16, si o jedničku zvětším, takže dostanu 17. Pod indexem i + 1 se skrývá jiné číslo, o tolik vyšší, kolik má kapitola řádků! Bude to třeba 999. A toto číslo si o jedničku zmenším. Když tato čísla použiju k výběru z vektoru všech řádků v románu, dostanu právě řádky textu jedné kapitoly, bez jejího nadpisu a bez nadpisu následující kapitoly. Tohle je velmi důležitá úvaha, a jestli je na tom něco nejasného, zkuste si nakreslit, co se děje.

Až takhle dojdu k poslední skutečné kapitole (číslo 135), bude mým i číslo 135 a ještě si sáhnu pro číslo 136 jako i + 1, což je ten přidaný řádek END. Na číslo 136 už můj for-cyklus nesmí skočit, jinak skript spadne. Kdybychom chtěli cyklus omezit úplně natvrdo známým počtem kapitol, mohli bychom napsat:

# for (i in 1:135){
# vezmi chap.positions.v[i], přičti jedničku 
# a na pozici vektoru novel.lines.v s výsledným číslem zahaj operaci.
# Vezmi chap.positions.v[i + 1], odečti jedničku
# a na pozici vektoru novel.lines.v s výsledným číslem ukonči operaci.
# }

Tím by se sice uvnitř cyklu operovalo se 136. pozicí vektoru chap.positions, protože bychom si na ni “sáhli” z pozice 135, ale nikdy by se nestala tím i z kulaté závorky v záhlaví for-cyklu a nemuselo by se sahat po neexistující pozici 137, a o to jde!

Když úvahu zobecníme, poslední pozice v opakování musí být předposlední pozicí vektoru chap.positions. To se přece dá vyjádřit jako length(chap.positions) - 1! Zkusíme si dotyčné řádky vytisknout. Kvůli přehlednosti kódu budeme tisknout až od pozice 133. Poslední je END, předposlední je ta 135. kapitola s názvem. Co se vytiskne?

for (i in 133:length(chap.positions.v) - 1) {
  print(paste("Nadpis s indexem i: ", novel.lines.v[chap.positions.v[i]], "  Nadpis s indexem i + 1: ", novel.lines.v[chap.positions.v[i + 1]])) #schválně neodečítám tu jedničku, chci další nadpis!
}
## [1] "Nadpis s indexem i:  CHAPTER 132. The Symphony.   Nadpis s indexem i + 1:  CHAPTER 133. The Chase--First Day."
## [1] "Nadpis s indexem i:  CHAPTER 133. The Chase--First Day.   Nadpis s indexem i + 1:  CHAPTER 134. The Chase--Second Day."
## [1] "Nadpis s indexem i:  CHAPTER 134. The Chase--Second Day.   Nadpis s indexem i + 1:  CHAPTER 135. The Chase.--Third Day."
## [1] "Nadpis s indexem i:  CHAPTER 135. The Chase.--Third Day.   Nadpis s indexem i + 1:  END"

Malinká odbočka o podmínce

Tím bychom měli problém vyřešený, ale Jockers tu používá podmínku, asi čistě z pedagogických důvodů. Podmínka (condition) sestává z if, kulaté závorky s nějakým testem (podmínkou), který se evaluuje, a když vyjde TRUE, tak se provede všechno, co je ve složených závorkách. Takhle:

for (i in 133:length(chap.positions.v)) {
  if (i < length(chap.positions.v)) {
    print(paste("Nadpis s indexem i: ", novel.lines.v[chap.positions.v[i]], "  Nadpis s indexem i + 1: ", novel.lines.v[chap.positions.v[i + 1]]))
  }
}
## [1] "Nadpis s indexem i:  CHAPTER 133. The Chase--First Day.   Nadpis s indexem i + 1:  CHAPTER 134. The Chase--Second Day."
## [1] "Nadpis s indexem i:  CHAPTER 134. The Chase--Second Day.   Nadpis s indexem i + 1:  CHAPTER 135. The Chase.--Third Day."
## [1] "Nadpis s indexem i:  CHAPTER 135. The Chase.--Third Day.   Nadpis s indexem i + 1:  END"

Hodnota v proměnné i roste s každým průchodem cyklu o jedničku. Poslední průchod, kde i bude nižší číslo než délka vektoru pozic řádků s nadpisy, bude 135, protože vektor chap.positions má 136 elementů. Jakmile dojde ke 136. pozici, kde je pořadové číslo řádku s nadpisem “END”, podmínka i < length(chap.positions.v)) bude vyhodnocena jako FALSE. Příkazy ze složených závorek se neprovedou a začne se zpracovávat skript za cyklem.

Podmínka samozřejmě může existovat nezávisle na for-cyklu. Příklad:

a <- sample(10, 5) #vybere se náhodných pět čísel od 1 do 10
a
## [1] 1 9 4 6 7
if (sum(a) > 26) {
  print(paste("Součet je větší než 26, je to ", sum(a)))
} 
## [1] "Součet je větší než 26, je to  27"
if (sum(a) == 26) {
  print("Součet je 26")
}
if (sum(a) < 26) {
  print("Součet je menší než 26.")
}

Tady končí odbočka o podmínce. Musíme se k ní později vrátit, ještě jsme ji nevyčerpali. Můžete se zatím sami podívat na funkci ifelse a podmínkové konstrukty else if () {} a else {}, kterými se dá doplnit to if () {}, které jsme právě viděli.

Pokračování výroby frekvenčních slovníků pro jednotlivé kapitoly

Kombinace for-cyklu, podmínky, že se nesmí jako poslední začátek kapitoly brát řádek, který obsahuje “END”, a původního skriptu pro výrobu frekvenčního slovníku pro celou knihu naráz už můžeme sestavit celý skript. Jen ještě musíme vymyslet, kam uložit výsledky. Výsledkem pro každou kapitolu je tabulka table. Tyto tabulky table chceme mít uspořádané za sebou podle pořadí kapitol. Na to se hodí struktura seznam (list). Je to vlastně vektor, jehož elementy mohou být další datové struktury - vektory, tabulky, matice, i třeba další seznamy, a dokonce se můžou vyskytovat vedle sebe. Prvním elementem seznamu může být třeba tabulka, druhým třeba další seznam, atd. Tím se také liší od vektorů, jejichž elementy musejí být stejného datového typu (znaky, číslice, booleovské hodnoty), nebo se je R pokusí na společný datový typ převést, jak už jsme viděli.

Pro svoje frekvenční slovníky si tedy připravíme prázdný seznam. Ten se vytvoří funkcí list bez dalších argumentů. Ale počkat, když už jsme v tom, vytvoříme si dva prázdné seznamy. Jeden pro frekvenční slovník s absolutními frekvencemi slovních výskytů a druhý pro relativní frekvence slovních výskytů.

chapter.raws.l <- list()
chapter.freqs.l <- list()
for (i in 1:length(chap.positions.v)) {     # for-cyklus
  if (i != length(chap.positions.v)) {  # podmínka zajišťující, 
    #že poslední element vektoru chap.positions.v se už průchodu cyklem neúčastní
    chapter.title <- novel.lines.v[chap.positions.v[i]] #najdeme si řádek na i-té 
    # pozici, to je začátek naší kapitoly, uložíme si název kapitoly. Později s ním 
    # pojmenujeme výslednou frekvenční tabulku jako položku seznamu, abychom mohli seznam
    # pohodlně prohledávat pomocí názvů kapitol a jejich čísel
    start <- chap.positions.v[i] + 1 # s frek. slovníkem začneme na následujícím řádku
    end <- chap.positions.v[i + 1] - 1 # s frek. slovníkem skončíme na řádku 
    #před začátkem další kapitoly, která se bude zpracovávat v příštím průchodu cyklem
    chapter.lines.v <- novel.lines.v[start:end] #vymezíme si text kapitoly
    chapter.words.v <- tolower(paste(chapter.lines.v, collapse = " ")) # převedeme kapitolu 
    # na malá písmena
    # a hlavně zdrcneme řádky kapitoly do jednoho dlouhatánského řetězce. 
    # Kde byly konce řádků, budou teď mezery. Pro jistotu, abychom nespekli nějaká slova dohromady.
    chapter.words.l <- strsplit(chapter.words.v, "\\W") # roztrháme kapitolu na slova
    chapter.word.v <- unlist(chapter.words.l) # zbavíme se seznamu, chceme slova ve vektoru
    chapter.word.v <- chapter.word.v[which(chapter.word.v != "")] # vyčistíme vektor 
    # od prázdných elementů, které vznikly roztrháním
    chapter.freqs.t <- table(chapter.word.v) # a tady budeme mít tabulku
    chapter.raws.l[[chapter.title]] <- chapter.freqs.t # musíme ji uložit do seznamu tabulek 
    # s absolutními frekvencemi výskytu
    # zároveň jsme ji pojmenovali - přidávání do seznamu si ještě pořádně ukážeme potom
    chapter.freqs.t.rel <- 100*(chapter.freqs.t/sum(chapter.freqs.t)) # z tabulky absolutních
    # frekvencí si vyrobíme tabulku relativních frekvencí a převedeme výsledek na procenta 
    #(tj. čísla 0 - 100, ne 0 - 1)
    chapter.freqs.l[[chapter.title]] <- chapter.freqs.t.rel # tabulku relativních frekvencí 
    # přidáme jako pojmenovanou tabulku do seznamu pojmenovaných tabulek relativních frekvencí
  }
}

Prohlédneme si aspoň kousek výsledku. Ze seznamu tabulek absolutních frekvencí se podíváme na prvních 20 řádků tabulky z 4. kapitoly. Položka seznamu se při vybírání uvádí jako první, a když ještě vybíráme z jejího vnitřku, tj. tady z frekvenční tabulky pro příslušnou kapitolu, musí být položka seznamu v dvojitých hranatých závorkách. Následuje požadavek na položky zevnitř položky seznamu, které se uvádějí v jednoduchých hranatých závorkách. Viz příklady.

chapter.raws.l[[4]][1:20]
## chapter.word.v
##         21st            a         abed    ablutions        about 
##            1           46            2            1            4 
##   accelerate   accustomed affectionate    afternoon   afterwards 
##            1            1            1            1            3 
##      against         ages        ached     achieved        alive 
##            1            2            1            1            1 
##          all       almost   altogether       always    amazement 
##           10            1            1            1            1

Ze seznamu relativních frekvencí se podíváme na prvních 20 řádků tabulky ze 135. kapitoly.

chapter.freqs.l[[135]][1:20]
## chapter.word.v
##           a       abate       about accompanied         act     advance 
##  1.56599553  0.02033760  0.10168802  0.04067521  0.02033760  0.02033760 
##   advancing      affect      afloat       after  afterwards       again 
##  0.04067521  0.02033760  0.02033760  0.06101281  0.02033760  0.30506406 
##     against         age      agents        ages         ago       agony 
##  0.08135042  0.02033760  0.02033760  0.02033760  0.02033760  0.02033760 
##        ahab         air 
##  0.75249136  0.10168802

Pokud skript nespouštíte pomocí Knit, ale pěkně ve vašem pracovním adresáři, napíšete název seznamu a za něj dolar a stisknete tabelátor, našeptávač vám nabídne všechny názvy kapitol. V jednoduché hranaté závorce si můžete vyžádat konkrétní elementy z té seznamové položky, tj. zde prvních deset frekvencí ve frekvenční tabulce pro kapitolu CHAPTER 2. The Carpet-Bag..

chapter.freqs.l$`CHAPTER 2. The Carpet-Bag.`[1:10]
## chapter.word.v
##           a  aboriginal       about adventurous  afterwards       again 
##  3.57880248  0.06882312  0.27529250  0.06882312  0.06882312  0.06882312 
##     against         ago         air         all 
##  0.13764625  0.06882312  0.06882312  0.13764625

Úkol splněn, máme seznamy frekvenčních tabulek. Toto je zároveň řešení vašeho domácího úkolu.

save(chapter.raws.l, file = "chapter.raws.l.RData")
save(chapter.freqs.l, file = "chapter.freqs.l.RData")