R-ohjelmointi.org

Tilastotieteellistä ohjelmointia R-kielellä

Suomen kielen aakkosten esiintymistaajuuksien selvittäminen

Kahdessa edellisessä (1, 2) postauksessa olen käsitellyt lyhyesti klassisia salakirjoitusmenetelmiä. Eräs salakirjoitusten avaamiseen yleisesti käytetty menetelmä on frekvenssianalyysi, jota voidaan tehokkaimmin hyödyntää, jos tunnetaan salatun tekstin alkuperäiskielen aakkosten suhteelliset osuudet. Miten esiintymistaajuudet sitten voidaan selvittää? Seuraavassa on esitellään kolme erilaista mahdollisuutta.

1. Suomen kielen sanaston käyttö

Kirjainten taajuudet voidaan selvittää vaikkapa Kotuksen sanastoa käyttäen. Ladataan ensin valmiiksi R-objektiksi muokattu sanasto, pilkotaan se merkeiksi, ja lasketaan kunkin merkin taajuus näin laadistusta aineistosta:

load(url("https://github.com/jtuimala/TextMining/raw/master/fintm-data.RData"))
tab <- table(unlist(lapply(tolower(sanasto), function(x) strsplit(x, "")[[1]])))
tab2 <- data.frame(sort(tab[grep("[[:alpha:]]", names(tab))], decreasing=T))
tab2$prob <- tab2$Freq/sum(tab2$Freq)
tab2.sanasto <- tab2
print(tab2)
 
   Var1   Freq         prob
1     i 155658 1.167691e-01
2     a 152817 1.146379e-01
3     t 115500 8.664400e-02
4     s  99177 7.439906e-02
5     e  91950 6.897762e-02
6     n  86961 6.523505e-02
7     u  86949 6.522605e-02
8     k  85597 6.421183e-02
9     l  76982 5.774916e-02
10    o  74637 5.599003e-02
11    r  52253 3.919834e-02
12    ä  40951 3.071999e-02
13    m  39556 2.967351e-02
14    p  39049 2.929317e-02
15    v  31683 2.376746e-02
16    h  27950 2.096710e-02
17    y  27773 2.083432e-02
18    j  17484 1.311588e-02
19    d  10464 7.849721e-03
20    ö   9236 6.928519e-03
21    g   4140 3.105681e-03
22    f   2766 2.074955e-03
23    b   2528 1.896416e-03
24    c    534 4.005878e-04
25    z    143 1.072735e-04
26    w    138 1.035227e-04
27    x     73 5.476201e-05
28    q     34 2.550559e-05
29    é     20 1.500329e-05
30    à      9 6.751480e-06
31    è      6 4.500987e-06
32    ê      5 3.750822e-06
33    ñ      5 3.750822e-06
34    ü      4 3.000658e-06
35    á      2 1.500329e-06
36    å      2 1.500329e-06
37    â      1 7.501645e-07
38    ç      1 7.501645e-07
39    î      1 7.501645e-07
40    ô      1 7.501645e-07
41    û      1 7.501645e-07

Suomen kielen yleisimmät kirjaimet ovat sanaston perusteella järjestyksessä ”iatsenuklor”. Nämä yhdessä kattavat noin 81 % kaikista kirjaimista.

2. 2000-luvulla julkaistu kirjallisuus

Toisinaan teksti on helposti irrotettavissa PDF-tiedostoista. Käytetään tässä esimerkkinä Bioinformatiikan perusteet -kirjaa, joka ladataan ensin työpöydälle:

library(pdftools)
tmp  <- pdf_text("C:\\Users\\lenovo\\Desktop\\Bioinfo-laaja.pdf")
tmp2 <- gsub("\r\n", " ", tmp)
tab <- table(unlist(lapply(tolower(tmp2), function(x) strsplit(x, "")[[1]])))
tab2 <- data.frame(sort(tab[grep("[[:alpha:]]", names(tab))], decreasing=T))
tab2$prob <- tab2$Freq/sum(tab2$Freq)
tab2.kirja <- tab2
print(tab2)
 
   Var1  Freq         prob
1     a 83205 1.097748e-01
2     i 81940 1.081058e-01
3     t 76176 1.005012e-01
4     e 71846 9.478852e-02
5     n 65166 8.597540e-02
6     s 64479 8.506902e-02
7     o 41101 5.422575e-02
8     l 36197 4.775576e-02
9     k 34871 4.600632e-02
10    u 33380 4.403921e-02
11    ä 27567 3.636995e-02
12    m 26971 3.558363e-02
13    r 20716 2.733122e-02
14    v 17017 2.245102e-02
15    y 13942 1.839409e-02
16    p 13818 1.823049e-02
17    h 10713 1.413397e-02
18    j 10405 1.372762e-02
19    d  9824 1.296109e-02
20    g  6236 8.227336e-03
21    c  4445 5.864418e-03
22    b  2290 3.021264e-03
23    f  2241 2.956617e-03
24    ö  2113 2.787742e-03
25    w   572 7.546562e-04
26    q   324 4.274626e-04
27    x   284 3.746895e-04
28    z   115 1.517228e-04
29    å     6 7.915975e-06
30    á     1 1.319329e-06

Kirjan perusteella yleisimmät kirjaimet ovat ’aitensolkuä’, jotka yhdessä vastaavat yli 80 % kaikista kirjaimista.

3. Gutenberg-projektin kirjallisuuden käyttäminen

Gutenberg-projekti säilöö kirjallisuutta, jonka tekijänoikeus on USA:ssa vanhentunut. R-paketti gutenbergr antaa mahdollisuuden ladata kirjallisuutta automaattisesti R:ään, minkä jälkeen siitä on helppo tehdä erilaisia analyysejä. Kannattaa huomata, että Gutenberg-projektin sivusto on tarkoitettu vain ihmiskäyttäjille, joten laajamittainen automaattisten työkalujen käyttö ei ole kannatettava ajatus. Poimin sivustolta muutamia kirjoja alla olevalla skriptillä, joka periaatteessa sallisi vaikka kaikkien siellä saatavilla olevien suomenkielisten kirjojen lataamisenkin. Vastuu skriptin käytöstä on jokaisella käyttäjällä, joten kannattaa huomioida projektin käyttösäännöt.

# Työkansio
setwd("C:/Users/lenovo/Desktop")
 
# Ladataan kirjallisuutta
library(gutenbergr)
mirror <- "http://www.gutenberg.org/"
 
# Mitkä kirjat ovat suomenkielisiä?
fi <- gutenberg_works(languages="fi")
ids <- fi$gutenberg_id
l <- vector("list", length(ids))
for(i in 1:length(ids)) {
   l[[i]] <- as.data.frame(gutenberg_download(ids[i], mirror="http://www.gutenberg.org/")  )
   Encoding(l[[i]]$text)<-"latin1" 
   print(i)
}
 
# Talletetaan teokset levylle
save(l, file="gutenberg_fi_latin1.RData")
 
# Pilkotaan teksti kirjamiksi, ja lasketaan niiden kirjakohtaiset frekvenssit
l2<-lapply(l, function(x) unlist(strsplit(tolower(x$text), "")))
l3<-unlist(l2)
 
library(plyr)
l4 <- lapply(l2, function(x) plyr::count(x))
 
# Yhdistetään kaikki kirjojen taulut yhteen
require(data.table)
l5 <- rbindlist(l4)
 
# Sama kuin yllä, mutta perus-R:ää käyttäen
l6 <- do.call("rbind", ll)
 
# Lasketaan yhteinen frekvenssitaulukko
l7<-tapply(l6$freq, list(l6$x), function(x) sum(x, na.rm=T))
 
# Objektiin d tulevat lopulliset tulokset
d<-data.frame(l7)
 
tab2 <- data.frame(sort(d[grep("[[:alpha:]]", rownames(d)),], decreasing=T))
colnames(tab2) <- "Freq"
tab2$prob <- tab2$Freq/sum(tab2$Freq)
tab2.gutenberg <- tab2
print(tab2)
 
      Freq         prob
a 36253155 1.165009e-01
i 34133419 1.096891e-01
n 30193909 9.702930e-02
t 28800210 9.255059e-02
e 24807400 7.971954e-02
s 22272149 7.157241e-02
l 18192664 5.846283e-02
ä 17195130 5.525722e-02
k 16288949 5.234517e-02
u 15252712 4.901518e-02
o 14970830 4.810934e-02
m  9928614 3.190599e-02
h  7814696 2.511283e-02
v  7264699 2.334539e-02
r  6722943 2.160444e-02
j  5688645 1.828068e-02
p  5377992 1.728239e-02
y  5053677 1.624019e-02
d  2230808 7.168788e-03
ö  1071089 3.441986e-03
g   463135 1.488302e-03
ã   304177 9.774846e-04
b   249309 8.011641e-04
c   195785 6.291627e-04
f   168376 5.410828e-04
w   140034 4.500047e-04
z    36341 1.167832e-04
x    28014 9.002408e-05
é    18940 6.086442e-05
q    14439 4.640029e-05
å    10077 3.238283e-05
î     9339 3.001124e-05
ï     4999 1.606448e-05
ü     4292 1.379251e-05
è     4040 1.298270e-05
ð     3606 1.158802e-05
á     3017 9.695247e-06
â     2472 7.943868e-06
ñ     2449 7.869957e-06
ê     1141 3.666648e-06
à     1137 3.653794e-06
ô      841 2.702586e-06
ç      798 2.564404e-06
ó      745 2.394086e-06
ë      696 2.236623e-06
æ      493 1.584275e-06
õ      333 1.070108e-06
í      165 5.302339e-07
ú      126 4.049059e-07
ò      101 3.245674e-07
ÿ      101 3.245674e-07
û       96 3.084997e-07
ù       50 1.606769e-07
ý       36 1.156874e-07
ø       24 7.712493e-08
ì        8 2.570831e-08

Gutenbergistä löytyvän kirjallisuuden perusteella yleisimmät kirjaimet ovat ”aintesläkuo”, jotka kattavat yli 80 % kaikista kirjaimista.

Eri lähteiden vertailu

Verrataanpa eri lähteiden tuloksia toisiinsa. Liitetään ensin kaikki taulukot toisiinsa, ja piirretään sen jälkeen pylväskaavio tuloksista.

tab2.gutenberg$Var1<-rownames(tab2.gutenberg)
tabf <- Reduce(function(x, y) merge(x, y, by="Var1", all=TRUE), list(tab2.sanasto, tab2.kirja, tab2.gutenberg))
 
tabf2 <- tabf[tabf$Var1 %in% c(letters, "å", "ä", "ö"),]
rownames(tabf2)<-tabf2$Var1
 
barplot((t(as.matrix(tabf2[,c(3,5,7)]))), beside=T, las=1, col=c("black", "#0000CC", "#CC0000"), border=c("black", "#0000CC", "#CC0000"), space=c(0.2,1.5))
abline(h=seq(0,0.1,by=0.02), col="white")
legend(x="topright", fill=c("black", "#0000CC", "#CC0000"), legend=c("sanasto", "kirja", "gutenberg"), bty="n")

Eri lähteiden välillä näyttää olevan eroja [klikkaa kuva suuremmaksi]:

freqplot

Erityisesti e, n ja ä näyttävät esiintyvät kirjallisuudessa useammin kuin sanastossa, ja toisaalta u, k ja r esiintyvät sanastossa useammin kuin kirjallisuudessa.

Jos kaikkien lähteiden tulokset lasketaan yhteen, on tulos seuraavanlainen:

finalres <- data.frame(freq=rowSums(tabf2[,c(2,4,6)]))
finalres$prop <- round(finalres$freq / sum(finalres$freq)*100, 3)
finalres[order(finalres$freq, decreasing=T),]
 
print(finalres)
      freq   prop
a 36489177 11.661
i 34371017 10.984
n 30346036  9.698
t 28991886  9.265
e 24971196  7.980
s 22435805  7.170
l 18305843  5.850
ä 17263648  5.517
k 16409417  5.244
u 15373041  4.913
o 15086568  4.821
m  9995141  3.194
h  7853359  2.510
v  7313399  2.337
r  6795912  2.172
j  5716534  1.827
p  5430859  1.736
y  5095392  1.628
d  2251096  0.719
ö  1082438  0.346
g   473511  0.151
b   254127  0.081
c   200764  0.064
f   173383  0.055
w   140744  0.045
z    36599  0.012
x    28371  0.009
q    14797  0.005
å    10085  0.003

Alla on lopullinen taulukko vielä sellaisessa muodossa, että sillä voidaan luoda finalres-objekti R:ään helposti:

finalres<-
structure(list(freq = c(36489177, 34371017, 30346036, 28991886, 
24971196, 22435805, 18305843, 17263648, 16409417, 15373041, 15086568, 
9995141, 7853359, 7313399, 6795912, 5716534, 5430859, 5095392, 
2251096, 1082438, 473511, 254127, 200764, 173383, 140744, 36599, 
28371, 14797, 10085), prop = c(11.661, 10.984, 9.698, 9.265, 
7.98, 7.17, 5.85, 5.517, 5.244, 4.913, 4.821, 3.194, 2.51, 2.337, 
2.172, 1.827, 1.736, 1.628, 0.719, 0.346, 0.151, 0.081, 0.064, 
0.055, 0.045, 0.012, 0.009, 0.005, 0.003)), .Names = c("freq", 
"prop"), row.names = c("a", "i", "n", "t", "e", "s", "l", "ä", 
"k", "u", "o", "m", "h", "v", "r", "j", "p", "y", "d", "ö", "g", 
"b", "c", "f", "w", "z", "x", "q", "å"), class = "data.frame")

Aineisto kattaa yhteensä n. 313 miljoonaa merkkiä kolmesta eri lähteestä. Vastaavanlaisia tilastoja löytyy muualtakin: jkorpelan-sivuilta, Practical Cryptography -sivustolta, ja (luonnollisesti) Wikipediasta. Tämä analyysi ja mainitut kolme muuta lähdettä antavat kaikki hieman toisistaan poikkeavia tuloksia. Vaikka yleisimmät kirjaimet ovat toki kaikissa lähteissä tismalleen samat, niiden keskinäiset runsaussuhteet vaihtelevat hieman.


Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *

Category