Stochastic Shakespeare: Sonnets Produced by Markov Chains in R

Malcolm Barrett

2018-01-25

Update with markovifyR

Thanks to Maëlle Salmon, who referred me to this post by Julia Silge and Nick Larsen, I explored doing this using the markovifyR package, and the results are unbelievable. See the bottom of the post for an updated batch of sonnets!

Original post

I recently saw Katie Jolly’s post, in which she produced Rupi Kuar-style poems using Markov Chains in R. I absolutely loved it, so I decided to try it with Shakespeare’s 154 sonnets using her post as a skeleton.

Downloading and cleaning the sonnets

In addition to markovchain and tidyverse, I’m going to use the gutenberger package to download the sonnets.

library(gutenbergr)
library(tidyverse) 
library(markovchain) 
shakespeare <- gutenberg_works(title == "Shakespeare's Sonnets") %>% 
  pull(gutenberg_id) %>% 
  gutenberg_download(verbose = FALSE)

shakespeare
## # A tibble: 2,625 x 2
##    gutenberg_id text                                          
##           <int> <chr>                                         
##  1         1041 THE SONNETS                                   
##  2         1041 ""                                            
##  3         1041 by William Shakespeare                        
##  4         1041 ""                                            
##  5         1041 ""                                            
##  6         1041 ""                                            
##  7         1041 ""                                            
##  8         1041 "  I"                                         
##  9         1041 ""                                            
## 10         1041 "  From fairest creatures we desire increase,"
## # ... with 2,615 more rows

Because the sonnets are in gutenberger, they’re already in a nice format to work with. I just need to do a little cleaning up: like Katie, I removed the punctuation, but I also have to clear out the sonnet titles, which were Roman numerals, and some title info.

#  a little function to make life easier
`%not_in%` <- function(lhs, rhs) {
  !(lhs %in% rhs)
}

#  remove new lines symbol, sonnet Roman numerals, and punctation
#  and split into vector
bills_words <- shakespeare %>% 
  mutate(text = text %>% 
    str_trim() %>% 
    str_replace_all("--", " ") %>% 
    str_replace_all("[^[:alnum:][:space:]']", "") %>% 
    str_replace_all("^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$", 
                    "") %>% 
    str_to_lower()) %>% 
  filter(text %not_in% c("the sonnets", "by william shakespeare", "", " ")) %>% 
  pull(text) %>% 
  str_split(" ") %>% 
  unlist() 

I’m also going to extract the punctuation and assess how many of each there are for when I actually assemble the sonnets later.

punctuation <- shakespeare %>% 
  pull(text) %>% 
  str_extract_all("[^[:alnum:][:space:]']") %>% 
  unlist()

punctuation_probs <- punctuation[punctuation %not_in% c("-", "(", ")")] %>% 
  table() %>% 
  prop.table()

Fit the Markov Chain

Now fit the Markov Chain with the vector of words.

#  fit a Markov Chain
sonnet_chain <- markovchainFit(bills_words)
cat(markovchainSequence(n = 10, markovchain = sonnet_chain$estimate), collapse =  " ")

care i do not mine own presage incertainties now with

And finally, here are a few functions to piece together lines to make them look like a sonnet using the walk() function from purrr to print out the lines (since it’s a side effect). No, they’re not actually iambic pentameter :(

write_a_line <- function(n_lines = 1) {
  walk(1:n_lines, function(.x) {
  # put together lines of more or less average length
    lines <- markovchainSequence(n = sample(c(6:9), 1), 
                               markovchain = sonnet_chain$estimate) %>% 
      paste(collapse = " ")
  
  #  add end-of-line punctuation based on their occurence 
  end_punctuation <- ifelse(.x == n_lines, ".", 
                            sample(names(punctuation_probs), 
                                   size = 1, 
                                   prob = punctuation_probs))
  cat(paste0(lines, end_punctuation, "  \n"))
  })
}

psuedosonnet <- function() {
  walk(1:3, function(.x) {
    write_a_line(4)
    cat("  \n")
  })
  
  write_a_line(2)
}

Generating the sonnets

Let’s try it out.

Psuedosonnet 1:

set.seed(154)
psuedosonnet()

which physic did i toil all too grossly,
of happy you nor falls under the master;
o absence seem’d it fears no form form,
you would i break of the darling buds of.

of brass are my head where our fashion an,
stars of thy book of small glory to swear,
what wealth some child of the painter must die.
the fierce thing replete with that bears it.

seems your feature incapable of such seems seeing for,
in his spoil of trust and,
blood make me soon to thy days are bright,
in your worth held and i swear to wait.

beauty tempting her poor retention could with a,
and the treasure of may be.

Psuedosonnet 2:

psuedosonnet()

my spirit a separable spite take;
it then say so flatter the sessions of,
pen hearing you drink potions of one?
and more hath all art for.

that best acquainted with kings when thou,
where breath but their proud compare thou,
of mouthed graves will grind on thy,
fleece made and thy cheek and this.

and when that is so for’t lies where all,
so is this say that we?
against myself i read self the earth remov’d lord?
mother for now than spurring to please him.

and all determinate for thee in grecian tires are,
and eyes can see till the general evil still.

Psuedosonnet 3:

psuedosonnet()

debt and from accident it may!
shine bright in hope what he doth deceive,
their brave state with tears thou in whose,
muse doth well knows is crown’d crooked knife.

nor white when my body is more then her:
look in my oblation poor infant’s discontent,
sail to his memory but all the other,
the least yet like none lov’st thou.

creature the prey of words by adding one and:
esteem’d when it ten for i have,
bosoms fits but then how shall beauty and leap’d,
my pen hearing you will give them for what.

end doth ride with fulness tomorrow;
methinks i slept in thy trespass with.

Psuedosonnet 4:

psuedosonnet()

a dearer birth to divide the lovely,
lips and beauty lack of worms to die but,
and do i be belov’d of praise.
sail doth use is famish’d for.

by praising him but if this shalt find out,
sweet that i read self resemble creating,
grace you look upon the most which,
make sweet love put beside his beauty of your.

pen reserve their glory but these quicker elements,
be as thou that then thou art my love!
by the store to flow for his,
of thee but mend to the marigold at the.

time you doth live look for.
want nothing sweet self away and therefore.

Psuedosonnet 5:

psuedosonnet()

wh’r better angel in some in,
oaths of good turns to death my comfort,
directed then gentle gait making thus with.
of conscience hold in his spring.

like a zealous pilgrimage to my heart,
politic that through the joy behind,
proud livery so fast as high to set and,
could write of shame commits for her pleasure lost.

sweetest odours made for love when thou canst not.
featureless and there is impannelled a,
exceeded by our time thou shalt hap to wonder,
grief though rosy lips and heart wound.

thou repent yet do thy parts do our!
death do show it is daily new to.

Update

Alright, this time I’m going to try it with the markovifyR package. I’m basically going to do the same cleaning as above, but this time I’ll be putting entire sentences, punctuation and all, into the Markov model. The markovify_text() function also accepts start words, so I thought it might look good to start with a sample of 100 starting words from the sonnets and construct the lines from there.

library(markovifyR)
#  same as above, but maintain as sentences and keep punctuation
bills_sentences <- shakespeare %>% 
  mutate(text = text %>% 
    str_trim() %>% 
    str_replace_all("--", " ") %>% 
    str_replace_all("^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$", 
                    "") %>% 
    str_to_lower()) %>% 
  filter(text %not_in% c("the sonnets", "by william shakespeare", "", " "))

#  fit the Markov Chain
markovify_model <-
  generate_markovify_model(
    input_text = bills_sentences$text,
    markov_state_size = 2L,
    max_overlap_total = 25,
    max_overlap_ratio = .85
  )

#  generate a sonnet
markovify_sonnet <- function() {
  lines <- markovify_text(
      markov_model = markovify_model,
      maximum_sentence_length = 75,
      output_column_name = 'sonnet_line',
      count = 50,
      tries = 1000, 
      start_words = sample(generate_start_words(markovify_model)$wordStart, 100),
      only_distinct = TRUE,
      return_message = FALSE) %>% 
    filter(str_count(sonnet_line, "\\w+") > 5 & str_count(sonnet_line, "\\w+") < 10) %>% 
    slice(sample(1:n(), 14)) %>% 
    mutate(id = 1:n()) %>% 
    select(id, sonnet_line) 
  
   #  add a period to the last line if the last charachter isn't punctuation 
   #  that ends a sentence  
   last_line <- lines[lines$id == 14, "sonnet_line"]
   lines[lines$id == 14, "sonnet_line"] <- str_replace(last_line, 
                                                       ".$(?<!//.//!//?|[:alnum:])", ".")
   
   #  print in a sonnet-like format
   walk(1:14, function(.x) {
     cat(lines$sonnet_line[.x], " \n")
     
     #  add a space every four lines
     if (.x %% 4 == 0) cat("\n") 
   })
}

Markovify Sonnet 1:

markovify_sonnet()

unmoved, cold, and to this purpose, that her skill
hung with the time exchang’d,
suns of the fairest and most precious jewel.
hung with the bett’ring of the dead,

my mistress, when she walks, treads on the wind,
exceeded by the grave and thee.
cheered and checked even by the grave and thee.
till my bad angel fire my good allow?

am of my faults thy sweet self dost deceive:
they look into the beauty of thy deeds
nativity, once in the chronicle of wasted time
not from the thing they see;

no, neither he, nor his own heart;
feeding on that which gives thee releasing.

Markovify Sonnet 2:

markovify_sonnet()

had, having, and in my mind;
lo! in the rearward of a throned queen
our dates are brief, and therefore to be won,
or whether shall i live, supposing thou art old,

were to be belov’d of thee.
stirr’d by a part of me:
ere you were by my unkindness shaken,
suffering my friend and i am still with me;

resembling strong youth in his former might:
bearing thy heart, which i bring forth,
hast thou, the master mistress of my lovers gone,
what merit do i ensconce me here,

pitiful thrivers, in their youthful sap, at height decrease,
thus can my muse brings forth.

Markovify Sonnet 3:

markovify_sonnet()

beyond all date; even to thy fair name.
cannot dispraise, but in my mind,
when to the lark at break of day arising
thee have i not to trust;

feeds on the kingdom of the roses.
i cannot blame thee, for my stain.
now, while the world doth spend
when i was not counted fair,

wishing me like to the summer sweet,
no love, my love-suit, sweet, fulfil.
suns of the thing she would have express’d
no bitterness that i before have heard them told.

bearing the wanton burden of the roses.
ah! but those same tongues, that give thee memory.

Markovify Sonnet 4:

markovify_sonnet()

looking on thee in their skill,
haply i think my love and hate,
better becomes the grey cheeks of the roses.
tempteth my better angel from my self uprear,

with insufficiency my heart think that a several plot,
i make my love is a greater grief
whilst, like a winter hath my pen,
unlook’d for joy in that bosom sits

in the blazon of sweet silent thought
o! lest your true image pictur’d lies,
crowning the present, doubting of the shore,
i make my love thou usest;

feeding on that which he doth say,
hast thou, the master mistress of my true sight.

Markovify Sonnet 5:

markovify_sonnet()

of him, i’ll live in doubt,
lord of my flesh were thought,
nor can i grieve at grievances foregone,
nor dare i question with my moan;

but in the living record of your frown,
admit impediments. love is as mine,
cannot dispraise, but in a cool well by,
lord of my love, to thee resort.

till i see barren of new pride,
ten times happier, be it not said
too base of thee to this hell.
too base of thee to give?

making lascivious comments on thy part:
farewell! thou art all my grief.

Well, call me Shockedspeare.

Exit, pursued by a bear