Tuesday, January 28, 2020

A tribute to Yodayas - V: Syllable frequencies and Wordcloud


I have no idea about what NLP could do in the regular case of English or English-like languages and I’m learning as I go. So it is pretty obvious that my efforts to try to adapt the readily available tools of NLP to suit the Myanmar language would lead me into blind alleys or worse.
However, as the Myanmar saying goes, I would get more offerings of husked rice if I get lost. Well, it won’t be surprising if I often end up in dead-ends. For example, I tried to look into the co-occurrence of song phrases that I had explored in my last post and got miserable results. I should have guessed that it would be silly to do so because as I had noted there that those phrases were unique to each song. Hence, there would be no relation to phrases from other songs. For that reason I may have better luck working with syllables or their n-grams instead.
In one of my earlier posts, I suggested using naive tokenization with the tokens() function of the “quanteda” package and specifying “word” for segmentation. I thought it may be able to segment Myanmar Unicode text directly into syllables. So tried it here but it proved to be imperfect. You can see that the result could include a considerable number of two-syllable segments!
Ysong0.syll0 <- tokens(Ysong0.c, what = "word")
# str(Ysong0.syll0)
cat(Ysong0.syll0[[1]])
လွမ်း ပို အောင် ညို မင်းလွင် လေပြေ ပျိုး တိမ်ခိုး ဆင် မျှော် တိုင်း မှိုင်း လွင် ရွှေ ဘွ င် ကြုံ ရ ပင် နန်း သာ ထွေ နွေ ရာ မင်း ငယ် သာ သည် လေ အို ကြုံ ရ လွ မ်း ဘွ ယ် လှိုင် ကြွယ် သင်းကြူ ဂေါ် ဇင် ကြိုင်ပျံ့ ရှား ရွှေ ဆပ်သွား ခိုင် မဉ္ဇူ
Here I am using my own code for syllabification which uses the text being split into “characters” using the quanteda function tokens() as raw material.
Ysong0.t <- tokens(Ysong0.c, "character")
system.time({
Ysong0_syll <- list()
M <- length(Ysong0.t)
for (k in 1:M) {
    Ysong0_syll.k <- list()
    TEMP <- Ysong0.t[[k]][1]
    j <- 1
    L <- length(Ysong0.t[[k]])
    for(i in 2:L) {
        y <- grepl("[\u102b-\u102c\u1038-\u1039\u103a]",Ysong0.t[[k]][i])
        if (y == TRUE){
            TEMP <- paste0(TEMP,Ysong0.t[[k]][i])
        } else { 
            my.1 <- grepl("[\u1040-\u1049]", Ysong0.t[[k]][i])
            my.0 <- grepl("[\u1040-\u1049]", Ysong0.t[[k]][i-1])
            if (my.1 == TRUE){
                if (my.0 == TRUE){
                    TEMP <- paste0(TEMP,Ysong0.t[[k]][i])
                } else {
                    Ysong0_syll.k[[j]] <- TEMP
                    j <- j+1
                    TEMP <- Ysong0.t[[k]][i]
                }
            } else {
                if (my.0 == TRUE){
                    Ysong0_syll.k[[j]] <- TEMP
                    j <- j+1
                    TEMP <- Ysong0.t[[k]][i]
                } else {
                    # for stacked consonant
                    if (grepl("[\u1039]",Ysong0.t[[k]][i-1])==TRUE){
                        TEMP <- paste0(TEMP,Ysong0.t[[k]][i])
                    } else {
                        Ysong0_syll.k[[j]] <- TEMP
                        j <- j+1
                        TEMP <- Ysong0.t[[k]][i]
                    }
                }
            }
        }
    }
    if (i == L){
        Ysong0_syll.k[[j]] <- TEMP
    }
    Ysong0_syll[[k]] <- paste(unlist(Ysong0_syll.k))
}
})
   user  system elapsed 
   0.73    0.04    0.75 
This time around I’m using a laptop with 10th generation i7 processor, 16GB RAM, and Windows-10. So my “tortoise-code” worked fast; may be an overkill for this amount of data. Note that for my syllabification, I keep the syllables of the stacked consonants together and not split them up into individual syllables.
cat(Ysong0_syll[[17]])
ပန်း ဟေ ဝန် ခု နှစ် ခွေ မျှော် လေ ယောင် ညို ပြာ ရီ မှောင် တိမ် တောင် နီ လာ သွေး ဆွေး ဘွယ် သွယ် သွယ် ထွေ လာ နှော ရေ ယာဉ် ကြော ငွေ သောင် ယံ မှာ မောင် နှံ လွမ်း လို့ ကျွမ်း ကြွေ ပြန် ဖြေ လဲ ပြေ ဘဲ မှော် ရုံ မြေ ထွေ ထွေ ကြုံ လာ မြ လိုဏ် နန်း စိန် ဆောင် ရွှေ တောင် ငွေ ရောင် သန်း ဇာ မ ရမ်း မြိုင် တန်း သာ မာ လာ ဇီ ဝါ ငွေ ခွါ ကြိုင် နှံ့ ဖြာ သည် မှာ ကုံ ဆန်း တမ်း ဆို ယောင် မြောက် တောင် ဘယ် ဆီ မှန်း ရွေ သန်း ညာ မယ် ပျော် ဘွယ် ဟေ ဝန် ကန္တာ မြိုင် တွင်း ကြိုင် သင်း လေ ဂေါ် ရွှေ ဖီ စိမ်း ကြည် ဖြာ သွန် အိုင် မွန် မန္ဒာ ခင်း ယို ဆင်း တွေ တွေ စီး ကျ ပြွန် အိုင် ရေ ကြာ ထွေ ငါး မျိုး ဝတ် လျှိုး ဝတ် ဆံ စုံ မြိုင် ယံ မောင် နှံ ခြိမ့် အုန်း ရွှေ ပိ တုန်း ယူ သုံး စား ရှာ တ မာ ရွှေ ကြက် တော ဥ ဩ မြည် သံ မြိုင် ယံ ပြည် စိုး ရွှေ ဘိုး ခေါင် ငှက် တော် တွန် ကြော် ငြာ ကြွေး တွန် ကြော် ငြာ ကြွေး ခွံ နွေး လေ ယူ လို့ လူ လို ဟန် စုံ မြိုင် ယံ ဘာ သာ ဘာ သာ မြူး တယ် ဘာ သာ မြူး တယ်
The Yodaya song texts are tokenized into syllables and dfm created.
library(quanteda )
Ysong0.syll.dfm <- corpus(t(data.frame(lapply(Ysong0_syll, paste0, collapse = " ")))) %>%
  tokens("fastestword") %>%
  dfm
You can get the number of documents and features with ndoc() and nfeat(). The document names and feature names are obtained by using docnames() and featnames(). Here, the extra work involved with using Myanmar text in NLP is that you need to use cat() or utf8_print() to be able to see the entire piece of text.
ndoc(Ysong0.syll.dfm)
[1] 35
nfeat(Ysong0.syll.dfm)
[1] 867
docnames(Ysong0.syll.dfm) <- yodayaSongs$title 
utf8::utf8_print(docnames(Ysong0.syll.dfm))
 [1] "၁။ အလွမ်းဘွဲ့ယိုးဒယား"                   "၂။ ကန်ဆင်းယိုးဒယား"                  
 [3] "၃။ မန္တလေးမြို့ဘွဲ့ယိုးဒယား"               "၄။ ဘုန်းတော်ဘွဲ့ယိုးဒယား"                
 [5] "၅။ မန္တလေးမြို့ဘွဲ့ယိုးဒယား"               "၆။ မိုးဘွဲ့ယိုးဒယား"                    
 [7] "၇။ တောတောင်ဘွဲ့ယိုးဒယား"                "ဂ။ အလွမ်းဘွဲ့ယိုးဒယား"                  
 [9] "၉။ ရာမဇာတ်ထွက်သမင်လိုက်ယိုးဒယား"           "၁၀။ မြင်းတက်ယိုးဒယား"                
[11] "၁၁။ စက်တော်ချင်း"                    "၁၂။ တောဘွဲ့ယိုးဒယား"                  
[13] "၁၃။ အီနောင်ဇာတ်ဖြည့်ယိုးဒယား"            "၁၄။ တောဘွဲ့ယိုးဒယား"                  
[15] "၁၅။ တောဘွဲ့ယိုးဒယား"                   "၁၆။ မှော်ပြန်ယိုးဒယား"                
[17] "၁၇။ တောဘွဲ့ယိုးဒယား"                   "၁၈။ ရာမဇာတ်ထွက်ယိုးဒယား"              
[19] "၁၉။ တောဘွဲ့ယိုးဒယား"                   "၂၀။ ရေလားယိုးဒယား"                 
[21] "၂၁။ အီနောင်ဇာတ်ထွက်ယိုးဒယား"             "၂၂။ အီနောင်ဇာတ်ထွက်ယိုးဒယား"            
[23] "၂၃။ တောဘွဲ့ယိုးဒယား"                   "၂၄။ အလွမ်းဘွဲ့ယိုးဒယား"                 
[25] "၂၅။ တောဘွဲ့ယိုးဒယား"                   "၂၆။ ရာမဇာတ်ဖြည့်ယိုးဒယား"             
[27] "၂၇။ တောဘွဲ့ယိုးဒယား"                   "၂၈။ တောဘွဲ့ယိုးဒယား"                  
[29] "၂၉။ တောဘွဲ့ယိုးဒယား"                   "၃၀။ မန္တလေးမြို့ဘွဲ့ယိုးဒယား"             
[31] "၃၁။မင်းတုန်းမင်းတရားကြီးဘုန်းတော်ဘွဲ့ယိုးဒယား" "၃၂။ အလွမ်းဘွဲ့ယိုးဒယား"                 
[33] "၃၃။ နန်းသိမ်းယိုးဒယား"                 "၃၄။ ရာမဇာတ်ဖြည့်ယိုးဒယား"             
[35] "၃၅။ ရာမဇာတ်ထွက်ယိုးဒယား"              
utf8::utf8_print(tail(featnames(Ysong0.syll.dfm)))
[1] "နား"   "လျှောက်" "ဪ"     "တုံ့"     "သယ်"    "ကြေး" 
The topfeatures() function will give you the features (here syllables) with the highest frquencies. And textstat_frequency() will give you the entire set of frequencies. But you need some sort of workaround to view the complete text in Myanmar language.
topfeatures(Ysong0.syll.dfm)
Ysong0.syll.dfm_tf <- textstat_frequency(Ysong0.syll.dfm)[,1:2]  %>%
   .[order(-.$frequency,.$feature),]
Let’s look at the 100 syllables with the highest frequencies and 100 with the lowest frequencies.
utf8::utf8_print(do.call("paste", c(sep= " = ", Ysong0.syll.dfm_tf[1:100,])))
  [1] "လေ = 148"  "ရွှေ = 142"  "သာ = 135"  "ကာ = 90"   "လာ = 86"  
  [6] "နန်း = 71"  "ရာ = 71"   "စုံ = 65"    "ဝေ = 65"   "ငယ် = 64"  
 [11] "တယ် = 57"   "တောင် = 56" "တော် = 54"  "မြိုင် = 54"  "မှာ = 53"  
 [16] "ပန်း = 48"  "ပူ = 47"    "မင်း = 44"  "စံ = 42"    "ဖန် = 42"  
 [21] "ဘုန်း = 41"  "မယ် = 41"   "အောင် = 41" "လွမ်း = 40"  "ထွေ = 39"  
 [26] "လို့ = 39"    "ပါ = 38"   "ဖြာ = 38"  "ဘွယ် = 38"   "ရေ = 38"  
 [31] "တော = 37"  "မ = 37"    "သည် = 37"   "သွယ် = 37"   "အို = 37"   
 [36] "ဆန်း = 36"  "လယ် = 35"   "ညာ = 34"   "ဘွေ = 34"   "ရ = 34"   
 [41] "ပျော် = 33" "မာ = 32"   "နေ = 31"   "ယံ = 31"    "ဝယ် = 31"  
 [46] "သင်း = 30"  "ကြာ = 28"  "က = 27"    "ထူး = 27"   "ဘယ် = 27"  
 [51] "သီ = 27"    "ကြိုင် = 26"  "မှုန် = 26"   "တ = 25"    "ခိုင် = 24"  
 [56] "နိုင် = 24"   "မြင် = 24"  "လှိုင် = 24"   "သူ = 24"    "ဟေ = 24"  
 [61] "စာ = 23"   "စီ = 23"    "ညို = 23"    "တိမ် = 23"   "နတ် = 23"  
 [66] "ပင် = 23"   "ပြန် = 23"  "ပြေ = 23"  "ဗျာ = 23"  "မူ = 23"   
 [71] "မောင် = 23" "မျှော် = 23" "မြေ = 23"  "ခွေ = 22"   "ဆောင် = 22"
 [76] "ဆော် = 22"  "တာ = 22"   "နယ် = 22"   "ပြည် = 22"  "မိုး = 22"  
 [81] "လှယ် = 22"   "ကယ် = 21"   "မန်း = 21"  "ယောင် = 21" "ရီ = 21"   
 [86] "ကြုံ = 20"   "ကြွယ် = 20"  "ညီ = 20"    "ဖူး = 20"   "ဖေါ် = 20" 
 [91] "ရန် = 20"   "ဟန် = 20"   "ကို = 19"    "ခါ = 19"   "တိုင်း = 19" 
 [96] "ဘုံ = 19"    "ရေး = 19"  "ရွေ = 19"   "သ = 19"    "ဦး = 19"  
utf8::utf8_print(do.call("paste", c(sep= " = ", Ysong0.syll.dfm_tf[768:867,])))
  [1] "ပွား = 1"   "ဖက် = 1"    "ဖမ်း = 1"   "ဖိုး = 1"    "ဖျ = 1"   
  [6] "ဖြစ် = 1"   "ဖြား = 1"  "ဖွါး = 1"   "ဖွေး = 1"   "ဗါ = 1"   
 [11] "ဘင် = 1"    "ဘယ့် = 1"    "ဘိ = 1"     "ဘိုး = 1"    "ဘော် = 1"  
 [16] "မတ် = 1"    "မန္တာ = 1"  "မလ္လာ = 1"  "မိန့် = 1"    "မိုက် = 1"   
 [21] "မုန္နာ = 1"  "မုန်း = 1"   "မေတ္တာ = 1" "မောက် = 1"  "မောင့် = 1" 
 [26] "မောင်း = 1" "မဲ = 1"     "မျောက် = 1" "မျှ = 1"    "မြစ် = 1"  
 [31] "မြေး = 1"  "မြဲ = 1"    "မြွက် = 1"   "မွှန်း = 1"   "မှည့် = 1"   
 [36] "ယီ = 1"     "ယီး = 1"    "ယု = 1"     "ယုန် = 1"    "ယော် = 1"  
 [41] "ရိ = 1"     "ရိုင်း = 1"   "ရို့ = 1"     "ရိုး = 1"    "ရီး = 1"   
 [46] "ရု = 1"     "ရူ = 1"     "ရော့ = 1"   "ရွ = 1"     "ရွေ့ = 1"   
 [51] "ရွှေ့ = 1"    "ရှည် = 1"    "ရှား = 1"   "ရှိန်း = 1"   "ရှေ့ = 1"   
 [56] "လင့် = 1"    "လင် = 1"    "လိ = 1"     "လုံ = 1"     "လုံ့ = 1"    
 [61] "လူ့ = 1"     "လျက် = 1"   "လျင် = 1"   "လျား = 1"  "လျှင် = 1"  
 [66] "လျှပ် = 1"   "လျှောက် = 1" "လွဲ = 1"     "လွှတ် = 1"    "လှူ = 1"    
 [71] "လှဲ = 1"     "ဝါး = 1"   "ဝိ = 1"     "ဝိုက် = 1"    "ဝေ့ = 1"   
 [76] "ဝှမ်း = 1"   "သင်္ကန်း = 1" "သင်္ခါ = 1"  "သင်္ဃို = 1"   "သစ္စာ = 1" 
 [81] "သဉ္စာ = 1"  "သန့် = 1"    "သမ္ဘာ = 1"  "သယ် = 1"    "သိင်္ဂ = 1"  
 [86] "သိန္ဒ = 1"   "သိန် = 1"    "သိန်း = 1"   "သြ = 1"    "သွဲ့ = 1"    
 [91] "ဟိန်း = 1"   "ဟု = 1"     "အဏ္ဏ = 1"   "အိုး = 1"    "အုန်း = 1"  
 [96] "အုံ့ = 1"     "အော် = 1"   "ဪ = 1"     "ဿန် = 1"    "၍ = 1"    
Now we draw a wordcloud of all the distinct syllables in the the corpus of 35 Yodaya songs. Note that Windows-10 included a Myanmar Unicode font named “Myanmar Text” and you don’t need specify the font in the textplot_wordcloud() function to draw a wordcloud with that font.
set.seed(2601)
textplot_wordcloud(Ysong0.syll.dfm,  min_size = .2, max_size = 9, min_count = 1, color = RColorBrewer::brewer.pal(8, "Dark2"))
If you have, for example, the Pyidaungsu font installed and you want to draw the wordcloud with it. Then you need to register this font as follows:
library(extrafont)
font_import(pattern="Pyidaungsu")
loadfonts(device="win")
library(extrafont)
loadfonts(device="win")
Myanmar Text already registered with windowsFonts().
Pyidaungsu already registered with windowsFonts().
Pyidaungsu Numbers already registered with windowsFonts().
After that you run the wordcloud() function by specifying that font:
set.seed(2601)
textplot_wordcloud(Ysong0.syll.dfm, font = "Pyidaungsu", min_size = .2, max_size = 9, min_count = 1, color = RColorBrewer::brewer.pal(8, "Dark2"))
The plot looks nicer with the Pyidaungsu font, but you certainly could tweak with the parameters of the textplot_wordcloud() function to get a better plot with the default font also.