Labádi Gergely </filológia>

Függelék

Honnan, miért?

A Géppel mért szövegek: Berzsenyi Dániel életműve és az új irodalomtudományos módszerek című tanulmány egy tavaly ősszel tartott konferenciaelőadás jelentősen átírt változata – jószerével csak a cím maradt meg. A tanulmányban bemutatott és értelmezett elemzések ellenőrizhetősége érdekében úgy döntöttem, hogy a használt kódokat egy függelékben kiadom. Használja mindenki szabadon.

Szövegelőkészítés

A következő script segítségével a Magyarlanc a Berzsenyi-kötet mind a 87 versét morfológiailag elemzi és külön fájlokba menti el.1 Parancssorból kell futtatni linuxos vagy maces gépen. Windows alatt csak akkor működik, ha a Cygwin nevű unix-szerű parancssori környezetben futtatjuk. Praktikus, ha a fájlok és a Magyarlanc egy könyvtárban vannak. A parancsok előtt álló szám nem a kód része, csak a könnyebb olvashatóság, és a kódok határainak egyértelműsítése érdekében használom.

1. for infile in $(ls -1 *.txt); do outfile=$(echo "out_${infile}"); echo $infile; echo $outfile; java -Xmx1G -jar magyarlanc-3.0.jar -mode morphparse -input ${infile} -output ${outfile}; done

A további lépéseket már R-parancsokkal kell végrehajtani. Én az RStudio-t használtam, amely minden platformon elérhető. Az RStudio működéséről elég sok tutoriált lehet találni, tehát az alapokat adottnak veszem. Beolvassuk az elemzendő fájlok nevét a filenames, magukat a Magyarlanc-cal elemzett fájlokat pedig a filelist nevű változóba. Értelemszerűen a filenames-nél azt az útvonalat kell megadni, ahol a beolvasandó fájlok találhatók, és érdemes odafigyelni, hogy csak ezeket a .txt-fájlokat tartalmazza, különben a többit is beolvassa. A filelist-be történő beolvasás pedig csak akkor fog működni, ha a munkakönyvtár ugyanaz, mint az előbb megadott útvonal.

2. filenames <- list.files(path="~/Documents/studies/berzsenyi_itk/berzsenyiversek", pattern="*.txt")
3. filelist <- lapply(filenames, function(x){read.csv2(x, header = FALSE, sep = "\t", stringsAsFactors = FALSE)})

A beolvasott fájlok második oszlopát, azaz a lemmatizált alakokat elmentjük a lemmak nevű változóba. Mivel a lemmak az írásjeleket is tartalmazzák, ezért ezeket kiejtjük és a csak lemmákat tartalmazó listát a csaklemmak nevű változóba mentjük.

4. lemmak = lapply(filelist, function(df){df["V2"]})
5. csaklemmak = lapply(lemmak, function(x){strsplit(as.character(x),"(\\W+)")})

Fogalmak keresése

Hogy könnyebben kereshető és elmenthető legyen az eredmény, a csaklemmak nevű listát átkonvertáljuk és elmentjük csaklemmak2 néven. Ez a változó 87 listát tartalmaz, a 87 vers lemmáit a versbeli felbukkanásuk sorrendjében. Ugyanakkor az írásjeltelenítés során minden lista (vers) élére egy „c” került a többszörös listabeágyazottság miatt. Ha egyenként töltjük be a fájlokat és exportáljuk az elemzés oszlopait, majd töröljük ki az írásjeleket, akkor nem kerülne bele. Bár a vizsgálatot magát nem zavarná, ezt érdemes kiszedni. Ezután lefuttatjuk a kereséseket és külön változókba mentjük.

6. csaklemmak2 = lapply(csaklemmak, function(x){unlist(x)})
7. csaklemmak2 <- lapply(1:length(csaklemmak2), function(x) csaklemmak2[[x]][csaklemmak2[[x]] != "c"])
8. nemek <- lapply(csaklemmak2,function(x) grep("\\bnem\\b|\\bsem\\b|\\bnincs\\b|\\bne\\b", x))
9. magyarok <- lapply(csaklemmak2, function(x) grep("magyar|hon|haza|báró|Róma", x))
10. istennok <- lapply(csaklemmak2, function(x) grep("múzs|cüpris|Cüpris|camoena|szerelem|ifjú|kebel|mirtus|myrtus|fohászkodás|labirinth|labirintus", x))

Mindhárom kereséshez létrehozunk egy újabb változót, amely ugyanolyan hosszú, mint az előző keresés, csak azokat a verseket, ahol találat van, 1-gyel jelöljük, ahol nincsenek, azt 0-val – ehhez kellenek az újabb változók.

11. magyarok2 <- magyarok
12. magyarok2[] <- as.numeric(sapply(magyarok, function(x) length(x) > 0))
13. istennok2 <- istennok
14. istennok2[] <- as.numeric(sapply(istennok, function(x) length(x) > 0))
15. nemek2 <- nemek
16. nemek2[] <- as.numeric(sapply(nemek, function(x) length(x) > 0))

Egy min nevű változóban összekapcsoljuk a különböző lekérdezéseket, és a jobb átláthatóság kedvéért a sorokat a versek címével elnevezzük. Ehhez előbb létre kell hozni egy cimek változót, amely a feldolgozás, tehát a kötet sorrendjében tartalmazza a címeket. Az oszlopok eredetileg a forrásváltozók nevét viselik („magyarok2”, „istennok2”, „nemek2”), ezt is átneveztem. A tanulmányban olvasható táblázatrészlet az így preparált min változóból származik. Ha akarjuk, ezt kimenthetjük magunknak, hogy táblázatkezelőben is használhassuk.

17. min <- cbind(magyarok2,istennok2,nemek2)
18. rownames(min) <- cimek
19. colnames(min) <- c("magyarok", "istennok", "nemek")
20. write.table(min, file = "mintablazat.csv", row.names=TRUE, sep="\t", quote=FALSE)

###Fogalmak korrelációja Ahhoz, hogy az előfordulások közötti statisztikai korrelációt kiszámolhassuk, a változóinkat át kell konvertálni listaváltozóból numerikussá, majd egy új mátrixszá (praktikusan: táblázattá) egybefűzni. Végül lefuttathatjuk a korrelációanalízist, és egy új változóba elmentjük. A tanulmányban ez a táblázat olvasható.

21. magyarok3 <- unlist(magyarok2)
22. istennok3 <- unlist(istennok2)
23. nemek3 <- unlist(nemek2)
24. min3 <- cbind(magyarok3,nemek3,istennok3)
25. min4 <- cor(min3)

A szócsoportok véletlenszerű elosztását és az így nyert adatok összefüggéseinek kiszámítását szintén egy új változóba kell menteni. Mivel egyszerre csak két érték randomizálását lehet az R-ben kezelni, ezért három futtatást kell végeznünk, de előtte a min3 változónkat data.frame adattípusúvá kell alakítanunk.

26. cor.df <- as.data.frame(min3)
27. mycors.v <- NULL
28. for(i in 1:10000){mycors.v <- c(mycors.v, cor(sample(cor.df$magyarok3), cor.df$istennok3))}
29. mycors2.v <- NULL
30. for(i in 1:10000){mycors2.v <- c(mycors2.v, cor(sample(cor.df$magyarok3), cor.df$nemek3))}
31. mycors3.v <- NULL
32. for(i in 1:10000){mycors3.v <- c(mycors3.v, cor(sample(cor.df$nemek3), cor.df$istennok3))}

A statisztikai alapadatokat érdemes külön is elmenteni – a tanulmányban ezeket tettem táblázatba. A tanulmányban látható grafikon parancsát is itt adom meg.

33. stat1 <- c(min(mycors.v),max(mycors.v),mean(mycors.v),sd(mycors.v))
34. stat2 <- c(min(mycors2.v),max(mycors2.v),mean(mycors2.v),sd(mycors2.v))
35. stat3 <- c(min(mycors3.v),max(mycors3.v),mean(mycors3.v),sd(mycors3.v))
36. h <- hist(mycors.v, breaks=100, col="grey", xlab="Korrelációs együttható", ylab="Gyakoriság", main="Magyarok és Istennők véletlenszerű eloszlása", plot=T) xfit <- seq(min(mycors.v),max(mycors.v),length=1000) yfit <- dnorm(xfit,mean=mean(mycors.v),sd=sd(mycors.v)) yfit <- yfit*diff(h$mids[1:2])*length(mycors.v) lines(xfit, yfit, col="black", lwd=2)

Kemény hangok

Innentől a „szép hanggal” kapcsolatos vizsgálatok R-parancsai olvashatók. Mivel a csakszavak2 első eleme (csakszavak2[[1]][1]) a csaklemmak2-höz hasonlóan egy „c”, ezt – mivel ennél a vizsgálatnál félrevezető volna – muszáj törölni. Szóval most törölni kell mindegyik vers elejéről. Azután kisbetűsítjük a szöveget az egyszerűbb kezelhetőség érdekében. Az alábbi kód csak a versek vizsgálatát mutatja be, de a tanulmányba beemelt kontrollanyagot is ugyanezekkel a parancsokkal elemeztem, egyedül a szétbontásnál (Ajánlás, Első könyv…) változtattam meg a változók neveit, ezért csak onnan jelzem a különbségeket.

37. szavak = lapply(filelist, function(df){df["V1"]})
38. csakszavak = lapply(szavak, function(x){strsplit(as.character(x),"(\\W+)")})
39. csakszavak2 = lapply(csakszavak, function(x){unlist(x)})
40. csakszavak2 <- lapply(1:length(csakszavak2), function(x) csakszavak2[[x]][csakszavak2[[x]] != "c"])
41. betuk <- lapply(1:length(csakszavak2), function(x) tolower(csakszavak2[[x]]))

Miután előkészítettük a szöveget, számoljuk, meg a mássalhangzókat, majd a magánhangzókat, majd készítsünk egy táblázatot, amely tartalmazza még a szóhosszúságot és a mgh/msh-arányt. Az oszlopokat feliratozzuk, a sorok neve pedig maga az adott szó. Az átláthatóság és a jobb összehasonlíthatóság érdekében kötetekre bontottam az adatokat. Az arányok kiszámolásánál a mássalhangzók számát osztottam el a magánhangzóékéval, fordítva is lehetne, de mindenképpen lesz néhány hely, ahol Inf, azaz végtelen az érték (ti. ha egyet nullával osztunk). Ezeket még a kötetekre bontás előtt kicseréltem nullára, hogyha később szórást, átlagot, szélsőértéket számolunk, ne zavarjon – mivel értelmezhetetlen, illetve egy részük konkrétan kiszámolhatatlan lesz.

42. msh_szamolas <- lapply(1:length(betuk), function(x) stri_count(betuk[[x]], regex = "(zs)|(th)|(ny)|(cz)|(ph)|(kh)|(ch)|b|(cs)|(gy)|(ty)|(sz)|(ly)|c|d|f|g|h|j|k|l|m|n|p|r|s|t|v|w|z"))
43. mgh_szamolas <- lapply(1:length(betuk), function(x) stri_count(betuk[[x]], regex = "(ae)|(oe)|a|á|e|é|i|í|o|ó|ö|ő|u|ú|ü|ű"))
44. msh <- lapply(1:length(msh_szamolas), function(x) as.matrix(msh_szamolas[[x]]))
45. mgh <- lapply(1:length(mgh_szamolas), function(x) as.matrix(mgh_szamolas[[x]]))
46. szamok <- lapply(1:length(msh), function(x) cbind(msh[[x]], mgh[[x]], msh[[x]]+mgh[[x]], msh[[x]]/mgh[[x]]))
47. for(i in seq_along(szamok)){colnames(szamok[[i]]) <- c("msh", "mgh", "szóhossz", "arányuk")}
48. for(i in seq_along(szamok)){rownames(szamok[[i]]) <- c(betuk[[i]])}
49. for(i in seq_along(szamok)){szamok[[i]][which(is.infinite(szamok[[i]]))] <- 0}
50. ajanlas <- as.data.frame(szamok[[1]])
51. elsokonyv <- do.call(rbind, szamok[2:27])
52. masodikkonyv <- do.call(rbind, szamok[28:53])
53. harmadikkonyv <- do.call(rbind, szamok[54:77])
54. negyedikkonyv <- do.call(rbind, szamok[78:87])

Megnézhetnénk ezeket az értékeket is, de mivel szavanként elemzi az egyes köteteket, elég nehéz átlátni. Ezért összesítsünk.

55. ertekek_negyedikkonyv <- c(sum(negyedikkonyv[,1]), sum(negyedikkonyv[,2]), (sum(negyedikkonyv[,1])+sum(negyedikkonyv[,2]))/length(negyedikkonyv[,1]), sum(negyedikkonyv[,1])/sum(negyedikkonyv[,2]), range(negyedikkonyv[,1]), mean(negyedikkonyv[,1]), sd(negyedikkonyv[,1]), range(negyedikkonyv[,2]), mean(negyedikkonyv[,2]), sd(negyedikkonyv[,2]))
56. ertekek_harmadikkonyv <- c(sum(harmadikkonyv[,1]), sum(harmadikkonyv[,2]), (sum(harmadikkonyv[,1])+sum(harmadikkonyv[,2]))/length(harmadikkonyv[,1]), sum(harmadikkonyv[,1])/sum(harmadikkonyv[,2]), range(harmadikkonyv[,1]), mean(harmadikkonyv[,1]), sd(harmadikkonyv[,1]), range(harmadikkonyv[,2]), mean(harmadikkonyv[,2]), sd(harmadikkonyv[,2]))
57. ertekek_masodikkonyv <- c(sum(masodikkonyv[,1]), sum(masodikkonyv[,2]), (sum(masodikkonyv[,1])+sum(masodikkonyv[,2]))/length(masodikkonyv[,1]), sum(masodikkonyv[,1])/sum(masodikkonyv[,2]), range(masodikkonyv[,1]), mean(masodikkonyv[,1]), sd(masodikkonyv[,1]), range(masodikkonyv[,2]), mean(masodikkonyv[,2]), sd(masodikkonyv[,2]))
58. ertekek_elsokonyv <- c(sum(elsokonyv[,1]), sum(elsokonyv[,2]), (sum(elsokonyv[,1])+sum(elsokonyv[,2]))/length(elsokonyv[,1]), sum(elsokonyv[,1])/sum(elsokonyv[,2]), range(elsokonyv[,1]), mean(elsokonyv[,1]), sd(elsokonyv[,1]), range(elsokonyv[,2]), mean(elsokonyv[,2]), sd(elsokonyv[,2]))
59. ertekek_ajanlas <- c(sum(ajanlas[,1]), sum(ajanlas[,2]), (sum(ajanlas[,1])+sum(ajanlas[,2]))/length(ajanlas[,1]), sum(ajanlas[,1])/sum(ajanlas[,2]), range(ajanlas[,1]), mean(ajanlas[,1]), sd(ajanlas[,1]), range(ajanlas[,2]), mean(ajanlas[,2]), sd(ajanlas[,2]))
60. summa<- rbind(ertekek_ajanlas, ertekek_elsokonyv, ertekek_masodikkonyv, ertekek_harmadikkonyv, ertekek_negyedikkonyv)
61. colnames(summa) <- c("msh", "mgh", "átlag szóhossz", "msh/mgh", "min msh", "max msh", "átlag msh", "szórás msh", "min mgh", "max mgh", "átlag mgh","szórás mgh")
62. rownames(summa) <- c("Ajánlás", "Első Könyv", "Második Könyv", "Harmadik Könyv", "Negyedik Könyv")

Mint korábban jeleztem, a kontrollanyag előkészítése ugyanúgy zajlik, a különbség a ajanlas <- as.data.frame(szamok[[1]]) parancssortól kezdődik.

63. berzsenyi_ertekezes <- do.call(rbind, szamok[1])
64. berzsenyi_level <- do.call(rbind, szamok[2])
65. virag_ertekezes <- do.call(rbind, szamok[3])
66. virag_vers <- do.call(rbind, szamok[4])
67. ertekek_virag_vers <- c(sum(virag_vers[,1]), sum(virag_vers[,2]), (sum(virag_vers[,1])+sum(virag_vers[,2]))/length(virag_vers[,1]), sum(virag_vers[,1])/sum(virag_vers[,2]), range(virag_vers[,1]), mean(virag_vers[,1]), sd(virag_vers[,1]), range(virag_vers[,2]), mean(virag_vers[,2]), sd(virag_vers[,2]))
68. ertekek_virag_ertekezes <- c(sum(virag_ertekezes[,1]), sum(virag_ertekezes[,2]), (sum(virag_ertekezes[,1])+sum(virag_ertekezes[,2]))/length(virag_ertekezes[,1]), sum(virag_ertekezes[,1])/sum(virag_ertekezes[,2]), range(virag_ertekezes[,1]), mean(virag_ertekezes[,1]), sd(virag_ertekezes[,1]), range(virag_ertekezes[,2]), mean(virag_ertekezes[,2]), sd(virag_ertekezes[,2]))
69. ertekek_berzsenyi_level <- c(sum(berzsenyi_level[,1]), sum(berzsenyi_level[,2]), (sum(berzsenyi_level[,1])+sum(berzsenyi_level[,2]))/length(berzsenyi_level[,1]), sum(berzsenyi_level[,1])/sum(berzsenyi_level[,2]), range(berzsenyi_level[,1]), mean(berzsenyi_level[,1]), sd(berzsenyi_level[,1]), range(berzsenyi_level[,2]), mean(berzsenyi_level[,2]), sd(berzsenyi_level[,2]))
70. ertekek_berzsenyi_ertekezes <- c(sum(berzsenyi_ertekezes[,1]), sum(berzsenyi_ertekezes[,2]), (sum(berzsenyi_ertekezes[,1])+sum(berzsenyi_ertekezes[,2]))/length(berzsenyi_ertekezes[,1]), sum(berzsenyi_ertekezes[,1])/sum(berzsenyi_ertekezes[,2]), range(berzsenyi_ertekezes[,1]), mean(berzsenyi_ertekezes[,1]), sd(berzsenyi_ertekezes[,1]), range(berzsenyi_ertekezes[,2]), mean(berzsenyi_ertekezes[,2]), sd(berzsenyi_ertekezes[,2]))
71. summarum <- rbind(ertekek_berzsenyi_ertekezes, ertekek_berzsenyi_level, ertekek_virag_ertekezes, ertekek_virag_vers)
72. colnames(summarum) <- c("msh", "mgh", "átlag szóhossz", "msh/mgh","min msh","max msh", "átlag msh", "szórás msh", "min mgh", "max mgh", "átlag mgh","szórás mgh")
73. rownames(summarum) <- c("Berzsenyi-értekezés", "Berzsenyi-levél", "Virág-értekezés", "Virág-vers")
  1. Köszönöm öcsémnek, Labádi Máténak, hogy segített a script megírásában.