R-ohjelmointi.org

Tilastotieteellistä ohjelmointia R-kielellä

Tekstin generointi Markovin ketjulla – nimigeneraattori

Väestörekistrikeskus on julkaissut avoimena datana suomalaisten etunimitilaston. Tilastossa ovat kaikki suomalaisten etunimet, joita esiintyy vähintään kymmenellä henkilöllä. Aineisto käytettävissä CC BY 4.0 -lisenssillä.

Aineistolla saammekin värkättyä yksinkertaisen etunimigeneraattorin! Seuraavassa muodostettava generaattori perustuu Markovin ketjuun, joka sovitetaan merkeiksi pilkottuun nimiaineistoon. Sovitetun Markovin mallin perusteella voidaan sitten generoida uusia nimiä miltei rajattomasti. Katsotaanpa miten tämä onnistuu R:n paketilla markovchain.

Aineiston valmistelu

Seuraavassa luetaan aineisto Excel-tiedostosta R:ään, ja pilkotaan nimet lista-muotoiseksi objektiksi, jossa kukin listan komponentti sisältää yhden merkeiksi pilkotun nimen.

Alkuperäisessä tiedostossa on erikseen listattuna etunimet ja toiset nimet niin miehille kuin naisillekin, joten luetaan kukin tilasto erikseen omaan objektiinsa. Lopputuloksena on neljä listaa dm1, dm2, dn1, dn2, joissa m1/m2 vastaavat miehien ensimmäisiä ja toisia nimiä ja n1/n2 ovat vastaavat listat naisten nimille.

# Luetaan aineisto
library(rio)
m1<-import("C:\\Users\\lenovo\\Desktop\\Etunimitilasto-2017-04-06-VRK.xls", sheet=2)
m2<-import("C:\\Users\\lenovo\\Desktop\\Etunimitilasto-2017-04-06-VRK.xls", sheet=3)
n1<-import("C:\\Users\\lenovo\\Desktop\\Etunimitilasto-2017-04-06-VRK.xls", sheet=5)
n2<-import("C:\\Users\\lenovo\\Desktop\\Etunimitilasto-2017-04-06-VRK.xls", sheet=6)
 
# Pilkotaan aineisto listaksi
dm1<-strsplit(gsub("-", "", tolower(m1$Etunimi)), "")
dm2<-strsplit(gsub("-", "", tolower(m2$Etunimi)), "")
dn1<-strsplit(gsub("-", "", tolower(n1$Etunimi)), "")
dn2<-strsplit(gsub("-", "", tolower(n2$Etunimi)), "")

Mallin sovittaminen

Malli sovitetaan aineistoon markovchain-paketin funktiolla markovchainFit(). Sovitetaan kullekin sukupuoli- ja nimityypille (ensimmäinen / toinen) oma mallinsa:

library(markovchain)
fitm1 <- markovchainFit(data = dm1)
fitm2 <- markovchainFit(data = dm2)
fitn1 <- markovchainFit(data = dn1)
fitn2 <- markovchainFit(data = dn2)

Nimien generointi

Uusia nimiä voidaan generoida mallista esimerkiksi seuraavasti. Otetaan malliksi miesten etunimiin sovitettu mallia, josta generoidaan satunnaisella kirjaimella alkava nimi, jonka pituus on kuusi merkkiä (alkukirjain + pyydetty 5 kirjainta):

markovchainSequence(n=5, markovchain=fit$estimate, include.t0=TRUE)
[1] "k" "a" "n" "n" "e" "m"

Nimien arpominen on kuitenkin helpointa tehdä seuraavalla funktiolla, joka hoitaa eräitä asioita käyttäjän puolesta:

generoiNimi<-function(malli, pituus, alkukirjain="a") {
   if(!alkukirjain %in% c(letters, "å", "ä", "ö")) {
      stop("Parametrin alkukirjain pitää olla jokin suomalaisen aakkoston kirjaimista!")
   }
   alkukirjain<-tolower(alkukirjain)
   nimi <- paste(c(toupper(alkukirjain), markovchainSequence(n=pituus, markovchain=malli$estimate, t0=alkukirjain)), collapse="")
   return(nimi)
}

Funktion avulla on siis mahdollista arpoa tietyn mittainen, tietyllä alkukirjaimella alkava nimi tietystä aiemmin sovitetusta mallista, siis esimerkiksi miesten viiden merkin mittaisten etunnimien generointi tapahtuisi näin:

generoiNimi(fitm1, 4, alkukirjain="a")
#[1] "Ainos"

Useampia nimiä voi arpoa käyttämällä replicate funktiota. Seuraava esimerkki toistaa siis generoiNimi() -funktion kutsun kymmenen kertaa, ja tulostaa sitten tulokset vektoriksi:

replicate(10, generoiNimi(fitm1, 4, alkukirjain="a"))
#[1] "Ahosp" "Atars" "Arzah" "Ariku" "Arono" "Avari" "Annyr" "Anric" "Alana" "Arerj"

Muita R:n perusfunktioita käyttäen on mahdollista generoi sekä ensimmäisiä että toisia nimiä, ja sitten muodostaa niistä kaikki mahdolliset yhdistelmät. Esimerkiksi Nelikirjaimisten j:llä alakavien etunimien ja viisikirjaimisten t:llä alkavien toisten nimien yhdistelmien tuottaminen voisi tapahtua esimerkiksi näin:

expand.grid(replicate(3, generoiNimi(fitm1, 3, alkukirjain="j")), replicate(3, generoiNimi(fitm2, 4, alkukirjain="t")))
 
#  Var1  Var2
#1 Jufa Ttint
#2 Jekk Ttint
#3 Jurr Ttint
#4 Jufa Tikat
#5 Jekk Tikat
#6 Jurr Tikat
#7 Jufa Tirso
#8 Jekk Tirso
#9 Jurr Tirso

Samaan tapaan naisten nimistä voidaan luoda yhdistelmiä:

expand.grid(replicate(3, generoiNimi(fitm1, 4, alkukirjain="s")), replicate(3, generoiNimi(fitm2, 6, alkukirjain="m")))
 
#   Var1    Var2
#1 Sandy Mmismin
#2 Sldya Mmismin
#3 Somua Mmismin
#4 Sandy Manoneo
#5 Sldya Manoneo
#6 Somua Manoneo
#7 Sandy Mondinp
#8 Sldya Mondinp
#9 Somua Mondinp

Loppupäätelmiä

Jos nyt sattuisi olemaan vaikka jälkikasvua tulollaan, niin tällä tavalla voitaisiin äkkiä luoda keskustelun pohjaksi erilaisia nimiyhdistelmiä. ”Jos se on poika, siitä tulee Jufa Tirso, ja jos se on tyttö, Somua Manoneo.” Toisaalta voisi vain generoida kaikki erilaiset yhdistelmät ensimmäisistä ja toisista nimistä ja käydä ne läpi. Eiköhän niistäkin joku sopisi. Yhdistelmiä on tosin liki kymmenen miljonaa, joten siinäkin voi hetki vierähtää.

En tiedä onko tälle varsinaisesti mitään käyttöä, mutta tulipahan ainakin muistuteltua mieleen, miten R:ssä estimoidaan Markovin prosessien parametrejä. Halusin oikeastaan alunperin muodostaa PWM (position weight matrix) -matriisin etsiäkseni tekstiaineistoista suomalaisia nimiä, mutta harhauduin varsin pahasti sivuraiteille. Allekirjoittanut ”Jara Teroni” lupaa kuitenkin palata tuohon PWM-asiaan myöhemmin!

Tags:


Category