Indledning

Denne vignet er er skrevet som baggrund til en artikel om sproglige forskelle mellem Bibelen og Koranen. Ideen var, at bruge nogle enkle, kvantitative teknikker til tekstanalyse af Bibelen og Koranen som afsæt for en diskussion af forskelle og ligheder mellem de to bøger … og religioner.

Formålet med teksten her er for det første at give noter, detaljer, tal og figurer til interesserede.

For det andet er dokumentet her tænkt som inspiration og ressource for folk som selv arbejder med, eller har lyst til at begynde på det, som kaldes Natural Language Processing, NLP. Det er en fascinerenede disciplin, hvor matematik og statistik møder tekst og sprog.

Tilhører du den første gruppe, som er nysgerrig på emnet, kan du bare læse teksten eller det af den, du nu har lyst til.

Tilhører du den anden gruppe, med appetit på programmering, kan du klikke på knapperne “Code” til højre og se den R-code som ligger bag de tal og figurer, du ser. Du kan fx klikke på Code-knappen nu, og se hvilke pakker jeg har brugt (plus et par linjer med generelle instillinger).

library(tidyverse)         # Suite of nice packages 
library(tidytext)          # Text analysis the tidy way
library(quanteda)          # A very stong package for text analytics 
library(Hmisc)             # Some useful functions
library(scales)            # For scales in plots
library(openNLP)           # For part-of-speach tagging
library(knitr)             # For nice output
library(kableExtra)        # For even nicer output
library(text2vec)          # For word vectors

set.seed(1972)             # For reproducibility
theme_set(theme_minimal()) # For nice, clean, plots
options(scipen=999)        # To avoid scientific notation

Du er også velkommen til at klone det repository, hvor jeg opbevarer al data og kode, jeg har skrevet til dette projekt. (Inklusive den tekst du læser nu.)

Data

Jeg har brugt i alt fem tekster til analysen, alle sammen konverteret til analyserbare datafiler. Først og fremmest:

  • Bibelen, den autoriserede oversættelser fra 1992, Det Danske Bibelselskab. 2011.

  • Koranen, Den Klare Koran, Dansk koranoversættelse af Amér Majid, Forlaget Tronen, 2015.

Hist og pist (især til sentiment analyse, se senere) har jeg inddraget de tilsvarende engelske versioner:

  • Bibelen på engelsk, King James Version, fra Project Gutenberg.

  • Koranen på engelsk, klargjort til analyse af ZuhaibAli på Kaggle.

  • Endelig har jeg et par enkelte steder brugt Harry Potter-serien til sammenligning. Disse tekster findes i en særlig pakke til R her.

Hvis man gerne vil have endnu flere detaljer, kan man se programmerne code/01_get_data.R og code/02_clean_data.R i projektets repository. Sidstnævnte indeholder den præcise syntax, der er brugt til at rense teksterne, så de er klar til analyse.

Se navne på de analyseklare filer, og hvordan de kombineres, under “Code”.

# The Bible in Danish
bib <- readRDS("../data/bibelen_cleaned.rds") %>% 
  select(text, era = testament, book, chapter, verse)

# The Quran in Danish
kor <- readRDS("../data/koranen_cleaned.rds") %>% 
  select(text, era = time, book = part, chapter = surano, verse) %>% 
  mutate(book = as.character(book))

# King James version of the Bible
kjv <- readRDS("../data/bib_kjv_cleaned.rds") %>% 
  select(text, era = testament, book, chapter, verse)

# The Quran in English
qur <- readRDS("../data/quran_cleaned.rds") %>% 
  select(text, era = time, book = part, chapter = surano, verse) %>% 
  mutate(book = as.character(book))

# Combine the Danish Bible and Quran into one dataframe
holy <- bind_rows(bib, kor) %>% 
  mutate(collection = ifelse(str_detect(era, "Testamente"), "Bibelen", "Koranen"))

# Same thing for the English equivalents
holy_en <- bind_rows(kjv, qur) %>% 
  mutate(collection = ifelse(str_detect(era, "Testament"), "The Bible", "The Quran"))

# Housekeeping: We no longer need those
remove(bib, kor, kjv, qur)

Databehandling

Generelt er tilgangen til kvantitativ tekstanalyse (Natural Language Processing) den, at splitte teksten op i mindre bidder, som fx ord, som man så kan tælle og på anden vis analysere.

Nogle gange er det ikke ord, der er den relevante enhed, men måske ordpar eller tre-ordskombinationer, hvis man er interesseret i bestemte udtryk. Det kan også være, at man vil se på sætninger, eller vers – den sidste enhed er særlig relevant for tekster som Bibelen og Koranen.

Jeg har dannet en række forskellige tabeller af denne type.

# Various metrics in "tidy" format: doc x term (including tfidf and more)
tfidf <- holy %>% 
    unnest_tokens(output = word, text) %>%     
    anti_join(tibble(word = tm::stopwords(kind = "da")), by = "word") %>% 
    mutate(stem = SnowballC::wordStem(word, language = "da")) %>%
    group_by(stem, word) %>%
    mutate(n = n()) %>% 
    group_by(stem) %>% 
    mutate(n_total = n(),
           common_origin = first(word, order_by = -n),
           no_origins = n_distinct(word)) %>%
    count(era, collection, stem, name = "n_doc",
          n_total, common_origin, no_origins) %>%
    group_by(stem) %>%
    mutate(doc_count = n()) %>%
    group_by(era, collection) %>% 
    mutate(doc_total = sum(n_doc)) %>%
    ungroup() %>% 
    bind_tf_idf(term = stem, document = era, n = n_doc)

# words x stem x era (for checking how stemming works)
# "Era" = new/old testament; Quran Mekkan/Medinan
word_era <- holy %>% 
  unnest_tokens(word, text) %>% 
  mutate(stem = SnowballC::wordStem(word, language = "da")) %>% 
  count(era, word, stem) %>% 
  group_by(era) %>% 
  mutate(pct = n/sum(n) * 100) %>% 
  ungroup() %>% 
  arrange(-pct) %>%  
  mutate(word = factor(word, levels = rev(unique(word)))) 

word_era_en <- holy_en %>% 
  unnest_tokens(word, text) %>% 
  mutate(stem = SnowballC::wordStem(word, language = "en")) %>% 
  count(collection, era, word, stem) %>% 
  group_by(collection, era) %>% 
  mutate(pct = n/sum(n) * 100) %>% 
  ungroup() %>% 
  arrange(-pct) %>%  
  mutate(word = factor(word, levels = rev(unique(word)))) 

# bigram  x era 
bigram_era <- holy %>% 
  unnest_tokens(ngram, input = text, token = "ngrams", n = 2) %>%   
  count(era, ngram) %>% 
  group_by(era) %>% 
  mutate(pct = n/sum(n) * 100) %>% 
  ungroup() %>% 
  arrange(-pct) %>%  
  bind_tf_idf(term = ngram, document = era, n = n) %>% 
  mutate(ngram = factor(ngram, levels = rev(unique(ngram)))) 

# Create book numbers for Bible. For plotting some metrics by book number.
bib_book_no <- holy %>% 
  filter(collection == "Bibelen") %>% 
  distinct(book) %>% 
  mutate(bookno = row_number())

# words x stem x book/sura number 
word_book <- holy %>% 
  left_join(bib_book_no) %>% 
  mutate(book_sura_no = coalesce(bookno, as.integer(chapter))) %>% 
  unnest_tokens(word, text) %>% 
  mutate(stem = SnowballC::wordStem(word, language = "da")) %>% 
  count(collection, book_sura_no, word, stem) %>% 
  group_by(collection, book_sura_no) %>% 
  mutate(pct = n/sum(n) * 100) %>% 
  ungroup() %>% 
  arrange(-pct) %>%  
  mutate(word = factor(word, levels = rev(unique(word)))) 

# Group text in verses
verses <- holy %>% 
  unite(doc_id, book, chapter, verse, remove = FALSE) %>% 
  group_by(doc_id) %>% 
  mutate(text = paste0(text, collapse = " ")) %>% 
  distinct(doc_id, .keep_all = TRUE) %>% 
  ungroup() %>% 
  mutate(nchar = nchar(text))

Lad os som eksempel se på en bid af én af disse tabeller. Den indeholder en særlige vægt, nemlig inverse document frequency, opfundet af Karen Spärck Jones.

Her er en bid af tabellen:

tfidf %>% 
  select(era, stem, n_total, n_doc, tf, idf, tf_idf) %>% 
  filter(tf_idf > 0) %>% 
  arrange(-n_total) %>% 
  mutate_if(is.numeric, round, digits = 3)
## # A tibble: 15,957 x 7
##    era                  stem     n_total n_doc    tf   idf tf_idf
##    <chr>                <chr>      <dbl> <dbl> <dbl> <dbl>  <dbl>
##  1 Medina               allâh       2709  1689 0.066 0.693  0.046
##  2 Mekka                allâh       2709  1020 0.024 0.693  0.017
##  3 Det Nye Testamente   jesus       1067  1038 0.013 0.288  0.004
##  4 Medina               jesus       1067    24 0.001 0.288  0    
##  5 Mekka                jesus       1067     5 0     0.288  0    
##  6 Det Gamle Testamente israelit     802   789 0.003 0.288  0.001
##  7 Det Nye Testamente   israelit     802    12 0     0.288  0    
##  8 Mekka                israelit     802     1 0     0.288  0    
##  9 Det Gamle Testamente juda         758   708 0.003 0.693  0.002
## 10 Det Nye Testamente   juda         758    50 0.001 0.693  0    
## # … with 15,947 more rows

Hver række i tabellen repræsenterer én kombination af ordstamme og dokument. (Ordene er opdannet til små bogstaver og ordstamme, så “folk” og “folket” regnes for samme ord, ligesom “Landet” og “land” behandles ens.)

Første række i tabellen viser oplysninger om ordet “allâh” i dokumentet “Medina” (altså den del af Koranen, der omhandler åbenbaringer i Medina, ikke Mekka).

Kolonnerne i tabellen er som følger:

  • era: Teksten som ordstammen optræder i. Jeg har opdelt de to værker i fire tekster:

    • Det Gamle Testamente
    • Det Nye Testamente
    • Mekka (dvs. Koranen med åbenbaringer mens Mohammed levede i Mekka)
    • Medina (dvs. Koranen med åbenbaringer efter Mohammed flyttede til Medina)
  • stem: Ordstammen

  • n_total: Hvor mange gange ordstammen optræder i alle teksterne. “Allâh” ses i alt 2.709 gange.

  • n_doc: Hvor mange gange ordstammen optræder i den specifikke tekst altså fx Medina-koranen. Her ses “allâh” 1.689 gange.

  • tf, term frequency: Hvor stor en andel ordstammen udgør ud af alle ordstammer i dokumentet. I Medina-koranen står “allâh” for 6,6 procent af alle ord.

  • idf, inverse document frequency, et mål for et ords sjældenhed, udregnet efter hvor mange dokumenter vi i alt arbejder med (her fire) og hvor mange dokumenter, ordet optræder i. Vi tager logaritmen til forholdet mellem de to. Fordi ordet “allâh” optræder i to af vores fire dokumenter (Medina- og Mekka-koranen) bliver \(idf_{allâh} = log{(\frac{4}{2})}\approx 0,693\). Et ord som “dåb” optræder kun i Bibelens Det Nye Testamente og får dermed en tungere vægt: \(idf_{dåb} = log{(\frac{4}{1})}\approx 1,386\). Omvendt, ord der optræder i alle fire tekster, som “herren”, får en vægt på nul og tæller derfor slet ikke med: \(idf_{herren} = log{(\frac{4}{4})} = 0\)

  • tf_idf: Er blot \(if \cdot idf\) Sagt på en anden måde: Ikke alle ord tæller lige meget; de sjældne ord bærer mere betydning og tælles derfor flere gange, mens de almindelige ord ignoreres.

Diverse optællinger

Når vi har tabeller af denne type, er det let at foretage de optællinger, vi har lyst til.

Her er fx antal ord i alt, antal unikke (forskellige) ord samt den procentuelle fordeling af de to.

word_era %>% 
  group_by(era) %>% 
  summarise(n = sum(n),
            n_distinct = n_distinct(word)) %>% 
  ungroup() %>%
  mutate(pct_n = n / sum(n) * 100,
         pct_distinct = n_distinct / sum(n_distinct) * 100)  %>% 
  mutate_if(is.numeric, round, digits = 1)
## # A tibble: 4 x 5
##   era                       n n_distinct pct_n pct_distinct
##   <chr>                 <dbl>      <dbl> <dbl>        <dbl>
## 1 Det Gamle Testamente 491318      17621  60.8         49.4
## 2 Det Nye Testamente   168634       8560  20.9         24  
## 3 Medina                54999       4183   6.8         11.7
## 4 Mekka                 92762       5320  11.5         14.9

Til sammenlingning antal ord i Harry Potter-serien, bind for bind, (den engelske version):

# Compare with Harry Potter
read_rds("../data/all_hp_books.rds") %>% 
  unnest_tokens(word, text) %>% 
  count(bookno)
## # A tibble: 7 x 2
##   bookno      n
##    <int>  <int>
## 1      1  77875
## 2      2  85401
## 3      3 105275
## 4      4 191882
## 5      5 258763
## 6      6 171284
## 7      7 198906

Hvis vi vil se på antal af unikke ord i Bibelen og Koranen, uden at dele op i Nye/Gamle Testamente og Mekka/Medina, kan det gøres sådan her:

word_era %>% 
  group_by(Bog = ifelse(str_detect(era, "Testamente"), "Bibelen", "Koranen")) %>% 
  summarise(n = sum(n),
            n_distinct = n_distinct(word)) %>% 
  ungroup()
## # A tibble: 2 x 3
##   Bog          n n_distinct
##   <chr>    <int>      <int>
## 1 Bibelen 659952      20684
## 2 Koranen 147761       6898

Bibelen har mange flere unikke ord, altså et større ordforråd, men er også meget længere, så noget af forskellen kan skyldes, at Bibelen har mere plads til at introducere nye ord i. En enkel måde at tage højde for forskelle i længden mellem Bibelen og Koranen, er ved blot at tage en tilfældig bid af Bibelen svarende til Koranens længde. Det giver selvfølgelig ikke præcist samme resultat hver gang, men tæt på.

# Experiment: Compare random sample of equal sizes:
holy %>% 
  unnest_tokens(word, text) %>% 
  group_by(Bog = ifelse(str_detect(era, "Testamente"), "Bibelen", "Koranen")) %>% 
  sample_n(147761) %>% 
  summarise(n = n(),
            n_distinct = n_distinct(word)) %>% 
  ungroup()
## # A tibble: 2 x 3
##   Bog          n n_distinct
##   <chr>    <int>      <int>
## 1 Bibelen 147761      11177
## 2 Koranen 147761       6898

En måde at se på, hvilke bøger/sura som har et vokabularium der adskiller sig fra de andre, er ved at tælle, hvor mange ord der kun optræder i den enkelte bog/sura. (“Sura” er de 114 kapitler som Koranen er delt op i.) Første Krønikebog stikker helt af, målt på denne måde.

unique_words <- word_book %>% 
  group_by(collection, book_sura_no, stem) %>% 
  summarise(n = sum(n)) %>% 
  group_by(stem) %>% 
  mutate(n_books = n()) %>% 
  filter(n_books == 1) %>% 
  ungroup() %>% 
  count(collection, book_sura_no) %>% 
  ungroup() %>% 
  left_join(mutate(bib_book_no, collection = "Bibelen", book_sura_no = bookno)) %>% 
  arrange(-n)

unique_words %>% select(collection, book, n_distinct = n)
## # A tibble: 166 x 3
##    collection book                 n_distinct
##    <chr>      <chr>                     <int>
##  1 Bibelen    Første Krønikebog           441
##  2 Bibelen    Apostlenes Gerninger        275
##  3 Bibelen    Fjerde Mosebog              267
##  4 Bibelen    Første Mosebog              227
##  5 Bibelen    Esajas' Bog                 215
##  6 Bibelen    Ezekiels Bog                189
##  7 Bibelen    Josvabogen                  171
##  8 Bibelen    Jobs Bog                    157
##  9 Bibelen    Jeremias' Bog               151
## 10 Bibelen    Salmernes Bog               146
## # … with 156 more rows

Ordskyer som den herunder er ret populære, fordi de har en vis visuel appel, men er egentlig ikke så gode til analyse, fordi det er lidt vilkårligt, hvordan ordene arrangeres og skaleres. Det er svært undersøge bestemte ordhyppigheder mere præcist i figurerr af denne type. På den anden side inviterer de ligesom til, at man leder efter interessante ord i “skyen”.

# Create corpus class
holy_corpus <- corpus(data.frame(verses), 
                      docid_field = "doc_id", 
                      text_field = "text")

# Create document-feature matrix
dfm <- dfm(holy_corpus,
          remove = stopwords("da"),
          remove_punct = TRUE,
          remove_numbers = TRUE,
          ngrams = 1:3)

dfm_stem <- dfm_wordstem(dfm, language = "da")                      

# Word cloud
dfm_col <- dfm(holy_corpus, groups = "collection",
              remove = stopwords("da"),
              remove_punct = TRUE,
              remove_numbers = TRUE)

textplot_wordcloud(dfm_col, min_count = 10, random_order = FALSE,
                   rotation = .5,
                   min_size = 1,
                   color = RColorBrewer::brewer.pal(8, "Dark2"),
                   comparison = TRUE)

remove(dfm, dfm_col)

(Jeg har brugt R-pakken quantada til at danne ordskyen. Det er en formiddabel pakke til tekstanalyse. Se her.)

Her er nogle flere plot med almindelige ord:

# Most common words (no stemming)
word_era %>% 
  group_by(era) %>% 
  top_n(15, pct) %>% 
  ungroup() %>%
  ggplot(aes(word, pct, fill = era)) +
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y = "Procent") +
  facet_wrap(~era, ncol = 2, scales = "free") +
  coord_flip() +
  labs(title = "Ordhyppigheder, alle ord, alle bøjningsformer",
      subtitle = "Det er ikke særligt informativt blot at tælle alt. Uvigtige småord dominerer") 

# Words with highest tf_idf (stemmed)
tfidf %>%
  arrange(-tf_idf) %>%
  mutate(label = capitalize(stem),
         label = ifelse(stem != common_origin, 
                        paste0(label, " (", common_origin, ")"),
                        label),
         label = factor(label, levels = rev(unique(label)))) %>% 
  group_by(era) %>% 
  top_n(15, tf_idf) %>% 
  ungroup() %>%
  ggplot(aes(label, tf_idf, fill = era)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~era, ncol = 2, scales = "free") +
  coord_flip() +
  scale_y_continuous(breaks = pretty_breaks(n = 3)) +
  labs(x = NULL, 
       y = "Vægtet frekvens",
       title = "Ordstammehyppigheder, vægtet efter inverse document frequency",
      subtitle = "Metoden giver et mere sigende billede",
      caption = "Grafen viser ordstammer. Hvor ordet forekommer i flere bøjningsformer, er den mest almindelige form vist i parentes") 

# Export some data for plotting in datawrapper
tfidf_plot <- tfidf %>%
  #filter(stem != "allâh") %>% 
  arrange(-tf_idf) %>%
  mutate(label = capitalize(common_origin),
         label = factor(label, levels = rev(unique(label)))) %>% 
  group_by(era) %>% 
  top_n(5, tf_idf) %>% 
  ungroup() %>% 
  select(label, era, tf_idf) %>% 
  arrange(era, -tf_idf)

tfidf_plot %>%
  write_csv2("../output/tfidf_plot.csv")

  # Bigrams with highest tf_idf (stemmed)
bigram_era %>%
  arrange(-tf_idf) %>%
  group_by(era) %>% 
  top_n(15, tf_idf) %>% 
  ungroup() %>%
  ggplot(aes(ngram, tf_idf, fill = era)) +
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y = "tf-idf") +
  facet_wrap(~era, ncol = 2, scales = "free") +
  coord_flip() +
  scale_y_continuous(breaks = pretty_breaks(n = 3)) +
  labs(x = NULL, 
       y = "Vægtet frekvens",
       title = "Ordpar-hyppigheder, vægtet efter inverse document frequency",
      subtitle = "Tilfører de hyppige ord en smule kontekst") 

Man kan også se lidt nærmere på konteksten for særligt interessante ord; her 10 eksempler på brug af ordet sværd “sværd” og “straf”:

# Example of context
sword  <- kwic(holy_corpus, pattern = c("sværd", "sværdet"))
punish <- kwic(holy_corpus, pattern = c("straf", "straffe", "straffen"))

head(sword, 10)
##                                                                                              
##   [Første Mosebog_27_40, 3]                     Ved dit | sværd | skal du leve, og           
##  [Første Mosebog_34_25, 23]      Dinas brødre, hver sit | sværd | og trængte uhindret ind i  
##   [Første Mosebog_34_26, 9]     søn Sikem dræbte de med | sværd | , og så hentede de         
##  [Første Mosebog_48_22, 17] tog fra amoritterne med mit | sværd | og min bue.«               
##     [Anden Mosebog_5_3, 41]     ramme os med pest eller | sværd | .«                         
##    [Anden Mosebog_5_21, 31]         og har givet dem et | sværd | i hånden, så de            
##    [Anden Mosebog_15_9, 23]        dem; jeg trækker mit | sværd | , min hånd tilintetgør dem 
##    [Anden Mosebog_17_13, 5]   Sådan besejrede Josva med | sværd | amalekitterne og deres hær.
##    [Anden Mosebog_18_4, 20]   og reddede mig fra Faraos | sværd | .«                         
##   [Anden Mosebog_22_23, 13]       og jeg dræber jer med | sværd | , så jeres koner bliver
head(punish, 10)
##                                                                                       
##    [Første Mosebog_4_13, 8]     til Herren:» Min |  straf  | er for stor at bære      
##   [Anden Mosebog_21_19, 12]       ham, gå fri af |  straf  | , hvis den anden er      
##   [Anden Mosebog_21_28, 29]  ejer skal gå fri af |  straf  | .                        
##   [Anden Mosebog_32_34, 34]      dag, da jeg vil | straffe | dem for deres synd.      
##    [Tredje Mosebog_5_1, 31]  , skal han bære sin |  straf  | .                        
##   [Tredje Mosebog_5_17, 29] skyld og må bære sin |  straf  | .                        
##   [Tredje Mosebog_7_18, 45]     det, må bære sin |  straf  | .                        
##  [Tredje Mosebog_17_16, 15]    , må han bære sin |  straf  | .                        
##   [Tredje Mosebog_19_8, 10]     det, må bære sin |  straf  | , for han har vanhelliget
##  [Tredje Mosebog_20_17, 56]  . Han skal bære sin |  straf  | .

Optælinger af udvalgte ord

Det er nemt at se nærmere på statistikken for udvalgte ord. Her er nogle af de ord, der i figuren over idf-vægtet ordstamme-hyppighed, stak ud som særligt sigende. Vi ser fx at ordstammen “døb”, hyppigst kommmer fra ordet “døber” som optræder 52 gange i Det Nye Testamente (og kun der).

# Check selected words
selected_stems <- c("Sværd", "Tempel", "Præst", "Ypperstepræst", 
                    "Helligånd", "Døb", "Allâh", "O", "Troen",
                    "Jesus", "Kong") 

tfidf %>% 
  filter(stem %in% tolower(selected_stems)) %>% 
  select(era, stem, common_origin, n_total, tf_idf) %>% 
  arrange(stem, era) %>% 
  mutate_if(is.numeric, round, digits = 3) %>% 
  data.frame()
##                     era          stem     common_origin n_total tf_idf
## 1                Medina         allâh             allâh    2709  0.046
## 2                 Mekka         allâh             allâh    2709  0.017
## 3    Det Nye Testamente           døb             døber      52  0.001
## 4    Det Nye Testamente     helligånd       helligånden      89  0.002
## 5    Det Nye Testamente         jesus             jesus    1067  0.004
## 6                Medina         jesus             jesus    1067  0.000
## 7                 Mekka         jesus             jesus    1067  0.000
## 8  Det Gamle Testamente          kong             konge    2697  0.000
## 9    Det Nye Testamente          kong             konge    2697  0.000
## 10               Medina          kong             konge    2697  0.000
## 11                Mekka          kong             konge    2697  0.000
## 12   Det Nye Testamente             o                 o     350  0.000
## 13               Medina             o                 o     350  0.002
## 14                Mekka             o                 o     350  0.001
## 15 Det Gamle Testamente         præst           præsten     735  0.001
## 16   Det Nye Testamente         præst           præsten     735  0.000
## 17               Medina         præst           præsten     735  0.000
## 18 Det Gamle Testamente         sværd             sværd     432  0.001
## 19   Det Nye Testamente         sværd             sværd     432  0.000
## 20 Det Gamle Testamente        tempel            tempel     330  0.001
## 21   Det Nye Testamente        tempel            tempel     330  0.000
## 22   Det Nye Testamente         troen             troen     489  0.000
## 23               Medina         troen             troen     489  0.003
## 24                Mekka         troen             troen     489  0.001
## 25 Det Gamle Testamente ypperstepræst ypperstepræsterne     156  0.000
## 26   Det Nye Testamente ypperstepræst ypperstepræsterne     156  0.001

Zipf’s lov

Zipf’s lov er interessant. Den siger, at et ords hyppighed er omvendt proportional med ordets plads, når ordene rangordnes efter hyppighed. Hvis det hyppigste ord fx forekommer som 1 procent af alle ord, kan det forventes, at det næsthyppigste ord forekommer ½ procent af gangene og så fremdeles. Det sjove er, at lovmæssigheden gælder for næsten en hvilken som helst tekst.

Den nemmeste måde at se dette i praksis på, er ved at plotte ordene efter rangorden og frekvens i et dobbelt logaritmisk koordinatsystem.

theme_set(theme_minimal())

word_era %>% 
  group_by(era) %>% 
  mutate(rank = row_number()) %>% 
  ungroup() %>% 
  ggplot(aes(rank, pct, color = era)) + 
  geom_line(size = 1.1, alpha = 0.8) + 
  scale_x_log10() +
  scale_y_log10() +
  labs(x = "Rang (log-skala)",
       y = "Procent (log-skala)",
       title = "Alle fire tekster følger nogenlunde Zipf's lov",
       subtitle = "Ord plottet efter rangordning og hyppighed") +
  theme(legend.title = element_blank())

Vold og kærlighed

Hvis vi leder mere grundigt efter ord relateret fx til enten vold eller kærlighed, opstår et interessant mønster: Mest vold i Bibelen, mest kærlighed i Koranen. En svaghed ved denne optælling er, at jeg ikke fandt nogen god, objektiv, statistisk måde at vælge de to grupper af ord på, så i sidste ende blev det et subjektivt valg, hvilke ord der er voldsorienterede, og hvilke der er kærlighedsorienterede. (De præcise lister fremgår af koden.)

# Violence related stems (list compiled subjectively):
violence <- c("sværd", "våb", "krig", "krigsbyt", "kamp", "kampråb", 
              "vold", "voldsmænd", "hævn", "slå", "slået", "drab", "dræb", 
              "mord", "blod", "blodskyld", "blodhævn", "død", "dø", "ihjel", 
              "fjend", "ødelæg", "tilintetgør", "soldat", "hær", "hærskar")

violence_terms <- word_era %>% 
  filter(stem %in% violence) %>% 
  distinct(word) %>% 
  pull(word) %>% 
  as.character()

# Love related stems 
love <- c("kær", "tilgiv", "omsorg", "beskyt", "hjælp", "støt", 
          "trøst", "troskab", "elsk")

love_terms <- word_era %>% 
  filter(stem %in% love) %>% 
  distinct(word) %>% 
  pull(word) %>% 
  as.character()

# The tidy text way: word counts by era
love_table <- word_era %>% 
  mutate(violence = stem %in% violence,
         love = stem %in% love) %>%
  group_by(era, violence, love) %>% 
  summarise(n = sum(n)) %>%
  gather(dict, value, violence, love) %>% 
  group_by(era) %>% 
  mutate(pct = n / sum(n) * 100) %>% 
  ungroup() %>% 
  filter(value)

# Chart
love_table %>%
  ggplot(aes(x = era, y = pct, fill = dict)) +
  geom_col() +
  coord_flip() +
  scale_fill_discrete(breaks = c("love", "violence"),
                      labels = c("Kærlighed", "Krig/vold")) +
  labs(title = "Forekomst af ord relateret til krig/vold og til kærlighed",
       y = "Procent",
       x = NULL) +
  theme(legend.title = element_blank())

# Output for datawrapper
love_table %>% select(era, dict, pct) %>% 
  spread(dict, pct) %>% 
  mutate(era = str_replace(era, "Medina", "Koranen (Medina)")) %>%
  mutate(era = str_replace(era, "Mekka", "Koranen (Mekka)")) %>% 
  rename(Vold = violence, `Kærlighed` = love) %>% 
  write_csv2("../output/love_chart.csv")

# Most common love/violence words by era
special_words <- word_era %>% 
  mutate(violence = stem %in% violence,
         love = stem %in% love) %>%
  filter(violence|love) %>% 
  group_by(era, violence, love) %>% 
  top_n(5, n) %>% 
  ungroup() %>% 
  arrange(violence, love, era, -n)

De to tabeller herunder viser de hyppigste ord relateret til henholdsvis vold og kærlighed. Denne gang har jeg dannet tabeller, hvor hver række repræsenterer én kombination af dokument/ord/ordstamme. Så kan vi se eksempler på dannelsen af ordstammer (som jeg har brugt algoritmen fra SnowballC::wordStem() til). Ordstammen “død”, ses fx i tabellen afledt af tre forskellige former: død, døde og døden.

special_words %>% 
  filter(violence) %>% 
  select(-violence, -love) %>% 
  mutate_if(is.numeric, round, digits = 3) %>% 
  head(10)
## # A tibble: 10 x 5
##    era                  word       stem        n   pct
##    <chr>                <fct>      <chr>   <dbl> <dbl>
##  1 Det Gamle Testamente hærskarers hærskar   282 0.057
##  2 Det Gamle Testamente sværd      sværd     236 0.048
##  3 Det Gamle Testamente fjender    fjend     230 0.047
##  4 Det Gamle Testamente døde       død       223 0.045
##  5 Det Gamle Testamente blod       blod      214 0.044
##  6 Det Nye Testamente   døde       død       160 0.095
##  7 Det Nye Testamente   død        død        93 0.055
##  8 Det Nye Testamente   blod       blod       89 0.053
##  9 Det Nye Testamente   døden      død        73 0.043
## 10 Det Nye Testamente   ihjel      ihjel      68 0.04
special_words %>% 
  filter(love) %>% 
  select(-violence, -love) %>% 
  mutate_if(is.numeric, round, digits = 3) %>% 
  head(10)
## # A tibble: 10 x 5
##    era                  word      stem        n   pct
##    <chr>                <fct>     <chr>   <dbl> <dbl>
##  1 Det Gamle Testamente hjælp     hjælp     146 0.03 
##  2 Det Gamle Testamente elsker    elsk      122 0.025
##  3 Det Gamle Testamente troskab   troskab    82 0.017
##  4 Det Gamle Testamente hjælpe    hjælp      39 0.008
##  5 Det Gamle Testamente hjælper   hjælp      29 0.006
##  6 Det Nye Testamente   kærlighed kær       100 0.059
##  7 Det Nye Testamente   elsker    elsk       63 0.037
##  8 Det Nye Testamente   kære      kær        56 0.033
##  9 Det Nye Testamente   hjælp     hjælp      43 0.025
## 10 Det Nye Testamente   elske     elsk       40 0.024

Imperativer

Den nemmeste måde at få et indtryk af, hvilke af teksterne der er mest påskrivende, er at se på forkomsten af ord og ordpar som “skal”, “du skal” og “må ikke”.

Her ser vi først en liste over de fem hyppigste ordpar, fra hver tekst, som indeholder ordet “skal”:

bigram_era %>% 
  filter(str_detect(ngram, "skal")) %>% 
  group_by(era) %>% 
  top_n(5, pct) %>% 
  ungroup() %>% 
  arrange(era, -pct)
## # A tibble: 20 x 7
##    era                  ngram         n    pct       tf   idf   tf_idf
##    <chr>                <fct>     <int>  <dbl>    <dbl> <dbl>    <dbl>
##  1 Det Gamle Testamente du skal     649 0.139  0.00139  0     0       
##  2 Det Gamle Testamente skal du     568 0.121  0.00121  0     0       
##  3 Det Gamle Testamente de skal     438 0.0936 0.000936 0     0       
##  4 Det Gamle Testamente skal i      409 0.0874 0.000874 0     0       
##  5 Det Gamle Testamente skal være   340 0.0726 0.000726 0     0       
##  6 Det Nye Testamente   skal i      122 0.0759 0.000759 0     0       
##  7 Det Nye Testamente   i skal      119 0.0741 0.000741 0     0       
##  8 Det Nye Testamente   de skal      82 0.0510 0.000510 0     0       
##  9 Det Nye Testamente   skal være    79 0.0492 0.000492 0     0       
## 10 Det Nye Testamente   ikke skal    75 0.0467 0.000467 0     0       
## 11 Medina               skal de      50 0.0936 0.000936 0     0       
## 12 Medina               skal i       43 0.0805 0.000805 0     0       
## 13 Medina               deri skal    37 0.0693 0.000693 0.693 0.000480
## 14 Medina               de skal      36 0.0674 0.000674 0     0       
## 15 Medina               i skal       32 0.0599 0.000599 0     0       
## 16 Mekka                skal de      38 0.0431 0.000431 0     0       
## 17 Mekka                skal i       33 0.0375 0.000375 0     0       
## 18 Mekka                skal være    28 0.0318 0.000318 0     0       
## 19 Mekka                deri skal    26 0.0295 0.000295 0.693 0.000205
## 20 Mekka                de skal      22 0.0250 0.000250 0     0

Her er forekomsten af ordparret “må ikke”. Det forekommer langt hyppigere i Bibelen, især i Det Gamle Testamente.

bigram_era %>% 
  filter(str_detect(ngram, "må ikke"))
## # A tibble: 4 x 7
##   era                  ngram       n     pct        tf   idf tf_idf
##   <chr>                <fct>   <int>   <dbl>     <dbl> <dbl>  <dbl>
## 1 Det Gamle Testamente må ikke   401 0.0857  0.000857      0      0
## 2 Det Nye Testamente   må ikke    52 0.0324  0.000324      0      0
## 3 Medina               må ikke     4 0.00749 0.0000749     0      0
## 4 Mekka                må ikke     1 0.00114 0.0000114     0      0

Eftersom brugen af ordet “skal” oftest sker i formen “du skal” og “skal du”, giver det mening blot at tælle det ord, som et groft udtryk for, hvor imperative teksterne er. Eller vi kan se på ordparret “må ikke”.

word_era %>% 
  filter(word == "skal") %>% 
  group_by(era, word) %>% 
  summarise(pct = sum(pct)) %>% 
  ggplot(aes(era, pct)) +
  geom_col() +
  coord_flip() +
  labs(title = "Bibelen har klart flest påbud i form af ordet 'skal'",
       y = "Ordet 'skal' i procent",
       x = NULL)

bigram_era %>% 
  filter(ngram == "må ikke") %>% 
  group_by(era, ngram) %>% 
  summarise(pct = sum(pct)) %>% 
  ungroup() %>% 
  ggplot(aes(era, pct)) +
  geom_col() +
  coord_flip() +
  labs(title = "Bibelen har klart flest forbud i form af 'må ikke'",
       y = "Ordparret 'må ikke' i procent af alle ordpar",
       x = NULL)

Flere sjove ord

Lidt flere optællinger af interessante ord, som vi er stødt på undervejs. Én bestemt bog i Bibelen har fx en stærk forkærlighed for ordet “møggud”. (Vi undersøger her alle bøjningsformerne.)

word_book %>% 
  filter(stem == "møggud") %>% 
  left_join(bib_book_no, by = c("book_sura_no" = "bookno")) %>%
  mutate_if(is.numeric, round, digits = 3) 
## # A tibble: 9 x 7
##   collection book_sura_no word       stem       n   pct book           
##   <chr>             <dbl> <fct>      <chr>  <dbl> <dbl> <chr>          
## 1 Bibelen              26 møgguder   møggud    36 0.117 Ezekiels Bog   
## 2 Bibelen              12 møgguder   møggud     2 0.011 Anden Kongebog 
## 3 Bibelen              12 møgguderne møggud     2 0.011 Anden Kongebog 
## 4 Bibelen              26 møgguderne møggud     3 0.01  Ezekiels Bog   
## 5 Bibelen               3 møgguder   møggud     1 0.005 Tredje Mosebog 
## 6 Bibelen              11 møgguder   møggud     1 0.005 Første Kongebog
## 7 Bibelen              11 møgguderne møggud     1 0.005 Første Kongebog
## 8 Bibelen               5 møgguder   møggud     1 0.004 Femte Mosebog  
## 9 Bibelen              24 møgguderne møggud     1 0.003 Jeremias' Bog

Et centralt, religiøst ord som “tilgivelse” forekommer langt hyppigst i Koranen. (Vi finder ordstammen “tilgiv” og tæller alle bøjningsformerne):

# Tilgivelse
word_era %>% 
  filter(stem == "tilgiv") %>% 
  group_by(era, stem) %>% 
  summarise(n = sum(n), pct = sum(pct)) %>%
  mutate_if(is.numeric, round, digits = 3) 
## # A tibble: 4 x 4
## # Groups:   era [4]
##   era                  stem       n   pct
##   <chr>                <chr>  <dbl> <dbl>
## 1 Det Gamle Testamente tilgiv    75 0.015
## 2 Det Nye Testamente   tilgiv    58 0.034
## 3 Medina               tilgiv   154 0.28 
## 4 Mekka                tilgiv   105 0.113

Ordet “bryst” forekommer hyppigst i Højsangen i Bibelen som er ret erotisk. Ordet også i nogle af Koranens suraer, fx i nummer 94. Her kommer den relative hyppighed, i procent af kapitles ord, til at virke uforholdsmæssig høj, fordi det er et kort kapitel. (Ordet bruges iøvrigt ikke i en erotisk betydning her.)

word_book %>% 
  filter(stem  == "bryst") %>% 
  group_by(book_sura_no, stem) %>% 
  summarise(n = sum(n), pct = sum(pct)) %>% 
  left_join(bib_book_no, by = c("book_sura_no" = "bookno")) %>% 
  arrange(-pct) %>%
  mutate_if(is.numeric, round, digits = 3) 
## # A tibble: 24 x 5
## # Groups:   book_sura_no [24]
##    book_sura_no stem      n   pct book            
##           <int> <chr> <dbl> <dbl> <chr>           
##  1           94 bryst     1 1.61  <NA>            
##  2           22 bryst     9 0.405 Højsangen       
##  3           25 bryst     3 0.11  Klagesangene    
##  4           34 bryst     1 0.103 Nahums Bog      
##  5           28 bryst     3 0.072 Hoseas' Bog     
##  6           20 bryst     2 0.044 Ordsprogenes Bog
##  7           33 bryst     1 0.041 Mikas Bog       
##  8            6 bryst     2 0.034 Josvabogen      
##  9           18 bryst     5 0.032 Jobs Bog        
## 10           16 bryst     1 0.029 Nehemias' Bog   
## # … with 14 more rows

Sentiment analyse

En mere systematisk måde at indfange tonen eller “ladningen” i en tekst, er ved at bruge sentiment analyse, som grundlæggende slår ordene op i en tabel, som rummer værdier for, hvorvidt ordet er positivt eller negativt ladet. Værdierne er dannet ved hjælp af en statisisk model, og altås ikke et rent subjektivt valg. Her bruger vi AFINN, udviklet af Finn Årup Nielsen.

Denne gang bruger vi de engelske versioner, da jeg ikke kunne finde AFINN i en dansk verison, som jeg umiddelbart kunne bruge.

Her er en stikprøve fra AFINN med eksempler på værdier. Negative tal betyder at et ord er negativt ladet, positive tal … gæt selv.

word_era_en_sent <- word_era_en %>%
  left_join(get_sentiments("afinn")) 

# Examples
word_era_en_sent %>% 
  select(word, value) %>% 
  filter(!is.na(value)) %>% 
  sample_n(10)
## # A tibble: 10 x 2
##    word      value
##    <chr>     <dbl>
##  1 enemy        -2
##  2 jewels        1
##  3 granted       1
##  4 peace         2
##  5 committed     1
##  6 greatest      3
##  7 kill         -3
##  8 retreat      -1
##  9 smart         1
## 10 guilt        -3

Et lille problem er, at “God” og “Jesus” er positivt ladede, og da de to ord i sagens natur er overrepræsenterede i Bibelen, skævrider de billedet lidt. Derfor sorterer vi dem fra.

get_sentiments("afinn") %>% filter(word %in% c("god", "jesus"))
## # A tibble: 2 x 2
##   word  value
##   <chr> <dbl>
## 1 god       1
## 2 jesus     1

Sentimentanalysen herunder er simpelthen gennemsnitlige ladninger fra de fire tekster. Det Gamle Testamente lader til at være klart mest negativt ladet, Det Nye Testamente mest positivt.

word_era_en_sent %>% 
  filter(!is.na(value)) %>% 
  # Since "god" and "jesus" has positive sentiment, we exclude this
  filter(!word %in% c("god", "jesus")) %>% 
  group_by(era) %>% 
  summarise(sentiment = sum(n * value) / sum(n))
## # A tibble: 4 x 2
##   era           sentiment
##   <chr>             <dbl>
## 1 Medina          -0.0572
## 2 Mekka           -0.0455
## 3 New Testament    0.284 
## 4 Old Testament   -0.106

Topfem eksempler på ord fra de fire tekster, der især bidrager med positiv ladning til sentiment-analysen:

# Examples of words that contribute the most to sentiment
word_era_en_sent %>%  
  filter(!is.na(value)) %>% 
  # Since "god" and "jesus" has positive sentiment, we exclude this
  filter(!word %in% c("god", "jesus")) %>% 
  group_by(era, word) %>% 
  summarise(sentiment_sum = sum(n * value)) %>% 
  group_by(era) %>% 
  top_n(5, sentiment_sum) %>% 
  arrange(era, -sentiment_sum)
## # A tibble: 20 x 3
## # Groups:   era [4]
##    era           word   sentiment_sum
##    <chr>         <chr>          <dbl>
##  1 Medina        good             351
##  2 Medina        faith            177
##  3 Medina        best             138
##  4 Medina        reward           126
##  5 Medina        great            105
##  6 Mekka         good             483
##  7 Mekka         best             324
##  8 Mekka         mercy            232
##  9 Mekka         like             222
## 10 Mekka         reward           190
## 11 New Testament great            762
## 12 New Testament good             744
## 13 New Testament love             537
## 14 New Testament heaven           510
## 15 New Testament glory            354
## 16 Old Testament great           2124
## 17 Old Testament good            1416
## 18 Old Testament like            1150
## 19 Old Testament praise           660
## 20 Old Testament heaven           654

Ordklasser

Lidt mere kompliceret er det at finde ordklasser, altså hvorvidt et ord er et substantiv, et verbum etc. Til det formål bruger vi pakken OpenNLP, der indeholder en dansk algoritme til formålet.

# # Get tagset with descriptions
tagset <- read_delim("../data/tagset for Danish.txt", delim = "\t", skip = 2)

# Set annotators for danish
sent_token_annotator <- Maxent_Sent_Token_Annotator(language = "da")
word_token_annotator <- Maxent_Word_Token_Annotator(language = "da")
pos_tag_annotator    <- Maxent_POS_Tag_Annotator(language = "da")

# Function to annotate text and aggregates POS counts:
pos_count <- function (s, f) {
  # To avoid memory issues when annotating a large text,
  # this function will aggregate annotation to counts
  # of part-of-speech (word classes)

  pc <- NLP::annotate(s, f) %>%
    as.data.frame() %>%
    unnest() %>%
    filter(type == "word") %>%
    mutate(Tag = as.character(features)) %>%
    filter(Tag != "XP") %>% # Ignore punctuation tags
    count(Tag)

  return(pc)
}

# We create count of pos-values in smaller chunks to avoid
# running out of memory
pos_stat <- holy %>%
  mutate(id = paste(era, book, sep = "_")) %>%
  split(.$id) %>%
  map(~ pos_count(.$text, list(sent_token_annotator,
                               word_token_annotator,
                               pos_tag_annotator)))

# Combine the df's and aggregate to the level we want:
pos_stat <- pos_stat %>%
  bind_rows(pos_stat, .id = "id") %>%
  separate(id, into = c("era", "book"), sep = "_") %>%
  group_by(era, Tag) %>%
  summarise(n = sum(n)) %>%
  group_by(era) %>%
  mutate(pct = n / sum(n) * 100) %>%
  ungroup() %>%
  left_join(select(tagset, Tag, Danish, Description))

# Plot the result
pos_stat %>%
  ungroup() %>%
  arrange(-pct) %>%
  mutate(Danish = factor(Danish, levels = rev(unique(Danish)))) %>%
  ggplot(aes(x = Danish, y = pct, color = era, group = era)) +
  geom_point() +
  geom_line() +
  labs(title = "Det Gamle Testamente: Mange navneord og egennavne",
       subtitle = "Fordelingen af ordklasser i procent",
       x = NULL, y = "Procent") +
  coord_flip() +
  theme(legend.title = element_blank())

Er Bibelen og Koranen ens?

En interessant øvelse er, at finde sætninger fra Bibelen og Koranen som minder om hinanden. Kan man finde helt eller næsten identiske sætninger i de to bøger?

Tabellen herunder er skabt ved:

  1. At splitte teksterne op i sætninger.
  2. Repræsentere hver sætning som en document-feature-matrix, dfm, altså en vektor hvor hver element afspejer hyppigheden af ét bestemt ord.
  3. Matricen afpejler ordstammer som forekommer minimum to gange, og frekvenserne er vejet efter inverse-document-frequency-metoden som beskrevet tidligere.
  4. Kun sætninger med mere end 10 ord er talt med; der er en del uinteressante sammenfald mellem korte sætninger.
  5. Endelig bruges cosine similarity til at måle lighed mellem sætningerne. Det betyder, kort fortalt, at man ser på cosinus til vinklen mellem de vektorer, der repræsenterer sætningerne. En værdi på 1 svarer til identiske sætninger, mens en værdi på 0 svarer til sætninger, der ikke har nogen ord til fælles.
# We tokenize by sentences like this: First collapse text into
# two big character stringa, then split up by sentences with tokens()
holy_sents <- holy %>% 
  group_by(collection = if_else(str_detect(era, "Testament"), "Bibelen", "Koranen")) %>% 
  summarise(text = paste(text, collapse = " ")) %>% 
  corpus(text_field = "text", docid_field = "collection") %>% 
  tokens(what = "sentence") %>% 
  unlist() %>% 
  tibble(text = ., collection = names(.)) %>% 
  mutate(id = row_number(),
         collection = str_extract(collection, "[:alpha:]+"))

# Convert sentences to dfm
holy_dfm <- holy_sents %>%   
  corpus(text_field = "text", docid_field = "id") %>% 
  dfm(remove = stopwords("danish"), remove_punct = TRUE) %>%
  dfm_wordstem(language = "danish") %>% 
  dfm_trim(min_termfreq = 2) %>% 
  dfm_tfidf()

# Subset rows: Only consider sentences with at least X terms
holy_dfm <- holy_dfm %>% dfm_subset(rowSums(holy_dfm != 0) >= 11)

# Find similar sentences, using cosine similarity and tfidf
holy_simil   <- textstat_simil(holy_dfm, method = "cosine", min_simil = 0.4)

# Convert to symetrical matrix and remove redundant triangle (and diagonal) 
holy_simil_m <- as.matrix(holy_simil)
holy_simil_m[lower.tri(holy_simil_m, diag = TRUE)] <- NA

# Line up as a long df, joined with actual sentences
holy_simil_df <- holy_simil_m %>% 
  reshape2::melt(value.name = "cosine") %>% 
  as_tibble() %>% 
  filter(!is.na(cosine)) %>% 
  arrange(-cosine) %>% 
  left_join(holy_sents, by = c("Var1" = "id")) %>% 
  left_join(holy_sents, by = c("Var2" = "id"), suffix = c("1", "2"))

# We want to look at similar sentences *between* the two books 
similar_sentences <- holy_simil_df %>% 
  filter(collection1 != collection2) 

# Housekeeping: This one takes up a lot of memory, so remove
remove(holy_simil_m)
similar_sentences %>% 
  head(5) %>% 
  select(-starts_with("Var")) %>% 
  knitr::kable(digits = 2) %>% 
  kableExtra::kable_styling(bootstrap_options = "striped", full_width = F) %>% 
  column_spec(2, width = "30em") %>% 
  column_spec(4, width = "30em") 
cosine text1 collection1 text2 collection2
0.62 Den fjerde engel blæste i sin basun, og en tredjedel af solen og en tredjedel af månen og en tredjedel af stjernerne blev ramt, så en tredjedel af dem formørkedes, og dagen mistede en tredjedel af sit lys og natten ligeså. Bibelen Din Herre ved bestemt at du, og en del af dem der er med dig, står op (i bøn) hen ved to tredjedele af natten, halvdelen af den eller en tredjedel af den. Koranen
0.59 Deraf kan vi vide, at vi er af sandheden, og over for ham kan vi bringe vort hjerte til ro, hvad end vort hjerte fordømmer os for; thi Gud er større end vort hjerte og kender alt. Bibelen De sagde: "Vi ønsker at spise deraf, så vort hjerte kan finde ro, og for at vi kan vide at du har fortalt os sandheden, så vi kan være vidner derpå. Koranen
0.56 Sådan skal I gøre: Den tredjedel af jer, de præster og levitter, der kommer på sabbatten, skal være portvagter; den anden tredjedel skal være i kongens palads og den sidste tredjedel ved Jesod-porten, mens hele folkemængden skal være i forgårdene til Herrens tempel. Bibelen Din Herre ved bestemt at du, og en del af dem der er med dig, står op (i bøn) hen ved to tredjedele af natten, halvdelen af den eller en tredjedel af den. Koranen
0.54 Forordning om sabbatten Herren sagde til Moses: Sig til israelitterne: I skal holde mine sabbatter, for sabbatten er et tegn mellem mig og jer, slægt efter slægt, for at I skal vide, at det er mig, Herren, som helliger jer. Bibelen Spørg dem om byen der lå ved havet, da de begik overtrædelser på sabbatten: da deres fisk kom til dem i synlige stimer på sabbatten, mens de ikke kom den dag hvor de ikke holdt sabbat. Koranen
0.52 En tredjedel skal dø af pest eller omkomme af sult i dig; en tredjedel skal falde for sværd rundt om dig; en tredjedel vil jeg sprede for alle vinde, og dem vil jeg forfølge med draget sværd. Bibelen Din Herre ved bestemt at du, og en del af dem der er med dig, står op (i bøn) hen ved to tredjedele af natten, halvdelen af den eller en tredjedel af den. Koranen

Som vi kan se, kan vi ikke finde sætninger af denne længde, som minder ret meget om hinanden på tværs af Bibelen og Koranen. De største ligheder opstår, når to sætninger deler sjældne ord som “tredjedel”.

Det er dog værd at bemærke, at begrebet “sabbat”, som mange nok mest forbinder med jødedommen, både optræder i den kristne og muslimske tekst.

Semantiske ligheder

En sidste form for sproglig lighed vi undersøger, er på ordniveau, idet vi bruger den berømte word2vec metode. Ideen er at repræsentere hvert ord som en vektor udledt af, hvor ofte forskellige ord optræder sammen. Til det formål bruger vi en såkaldt feature co-occurence-matrix, fcm, som viser, hvor ofte hvert ord optræder i samme kontekst som andre ord.

Ideen er, at vektor-repræsentationen af ord kan have nogle nyttige egenskaber, som for eksempel at ord som har sematisk lighed, ligger relativt tæt på hinanden i ordvektor-rummet.

Normalt ville man foretrække et større korpus end Bibelen og Koranen, men for eksemplets skyld udleder vi her ordvektorer udelukkende på baggrund af disse to bøger.

Det lader til at give en vis mening; tabellen herunder viser hvilke ord, der ligger “tæt” på hinanden, når man bruger ordvektor-modellen.

Man bidder mærke i nogle oplagte ligheder: “gud” minder om “herren”, “jesu” minder om “kristi”, for eksempel. Der er også nogle knap så oplagte, som at “venstre” og “højre” er semantisk relaterede.

Den sidste tabel viser hvilke ord, der er tættest relateret til ordet “Gud”. Man kan blandet andet se, at Gud er ret snakkesagelig og godt kan lide at forklare ting. Ordene “siger” og “derfor” er semantisk relateret til “gud”.

related <- textstat_simil(holy_vectors, margin = "documents", 
                           method = "cosine", min_simil = 0.3)

most_related <- related %>% 
  data.frame() %>% 
  as_tibble() %>% 
  arrange(-cosine) 

most_related %>% head(30)
## # A tibble: 30 x 3
##    document1 document2 cosine
##    <fct>     <fct>      <dbl>
##  1 fornægter troen      0.914
##  2 venstre   højre      0.829
##  3 dette     siger      0.822
##  4 gud       herren     0.806
##  5 ikke      men        0.788
##  6 sagde     svarede    0.785
##  7 jesu      kristi     0.780
##  8 men       hvis       0.769
##  9 sagde     da         0.762
## 10 så        men        0.758
## # … with 20 more rows
most_related %>% filter(document1 == "gud") %>% head(10)
## # A tibble: 10 x 3
##    document1 document2 cosine
##    <fct>     <fct>      <dbl>
##  1 gud       herren     0.806
##  2 gud       herre      0.707
##  3 gud       derfor     0.645
##  4 gud       siger      0.631
##  5 gud       du         0.625
##  6 gud       jeg        0.605
##  7 gud       israels    0.604
##  8 gud       har        0.604
##  9 gud       din        0.599
## 10 gud       dig        0.595

Afslutning

Efter således at have brugt statistiske teknikker fra Natural Language Processing til at blive klogere på Gud, er det på tide at takke af.

Men der er naturligvis mange flere muligheder for analyser; her har vi blot kradset i overfladen. Forhåbentlig kan disse eksempler og kode-stumper være med til at inspirere andre til at grave dybere i de hellige tekster – med analyse.

Hvis du har spørgsmål, forslag, rettelser eller kommentarer, er du mere end velkommen til at bruge projektets repository, for eksempel til at oprette et issue eller en pull request.