Understanding Taylor Swift with Python

Here are the complete lyrics of Taylor Swift’s “Shake it off”, in the form of a Python string

shakeItOffComplete = """
I stay out too late
Got nothing in my brain
That's what people say, mm, mm
That's what people say, mm, mm
I go on too many dates
But I can't make them stay
At least that's what people say, mm, mm
That's what people say, mm, mm

But I keep cruisin'
Can't stop, won't stop movin'/groovin'
It's like I got this music in my mind
Saying it's gonna be alright

'Cause the players gonna play, play, play, play, play, 
And the haters gonna hate, hate, hate, hate, hate, 
Baby, I'm just gonna shake, shake, shake, shake, shake, 
I shake it off, I shake it off
Heartbreakers gonna break, break, break, break, break, 
And the fakers gonna fake, fake, fake, fake, fake, 
Baby, I'm just gonna shake, shake, shake, shake, shake, 
I shake it off, I shake it off

I never miss a beat
I'm lightning on my feet
And that's what they don’t see, mm, mm
That's what they don’t see, mm, mm
I'm dancing on my own (dancing on my own)
I make the moves up as I go (moves up as I go)
And that's what they don't know, mm, mm
That’s what they don’t know, mm, mm

But I keep cruisin'
Can't stop, won't stop movin'/groovin'
It's like I got this music in my mind
Saying it's gonna be alright

'Cause the players gonna play, play, play, play, play, 
And the haters gonna hate, hate, hate, hate, hate, 
Baby, I'm just gonna shake, shake, shake, shake, shake, 
I shake it off, I shake it off
Heartbreakers gonna break, break, break, break, break, 
And the fakers gonna fake, fake, fake, fake, fake, 
Baby, I'm just gonna shake, shake, shake, shake, shake, 
I shake it off, I shake it off

Shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off

Hey, hey, hey
Just think while you've been getting down and out about the liars
And the dirty, dirty cheats of the world
You could've been getting down
To this sick beat

My ex-man brought his new girlfriend
She's like, “Oh my God,” but I'm just gonna shake
And to the fella over there with the hella good hair
Won't you come on over, baby?
We can shake, shake, shake
Yeah, oh, oh, oh

'Cause the players gonna play, play, play, play, play, 
And the haters gonna hate, hate, hate, hate, hate, 
Baby, I'm just gonna shake, shake, shake, shake, shake, 
I shake it off, I shake it off
Heartbreakers gonna break, break, break, break, break, 
And the fakers gonna fake, fake, fake, fake, fake, 
Baby, I'm just gonna shake, shake, shake, shake, shake, 
I shake it off, I shake it off

Shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off

Shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off

Shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off
"""
print( type(shakeItOffComplete))

By representing it in Python we can learn about the formulae underlying pop music. Let’s break it up into parts to see how it is structured.

verse1 = """
I stay out too late
Got nothing in my brain
That's what people say, mm, mm
That's what people say, mm, mm
I go on too many dates
But I can't make them stay
At least that's what people say, mm, mm
That's what people say, mm, mm
"""

prechorus = """
But I keep cruisin'
Can't stop, won't stop movin'/groovin'
It's like I got this music in my mind
Saying it's gonna be alright
"""

chorus = """
'Cause the players gonna play, play, play, play, play, 
And the haters gonna hate, hate, hate, hate, hate, 
Baby, I'm just gonna shake, shake, shake, shake, shake, 
I shake it off, I shake it off
Heartbreakers gonna break, break, break, break, break, 
And the fakers gonna fake, fake, fake, fake, fake, 
Baby, I'm just gonna shake, shake, shake, shake, shake, 
I shake it off, I shake it off
"""

verse2 = """
I never miss a beat
I'm lightning on my feet
And that's what they don’t see, mm, mm
That's what they don’t see, mm, mm
I'm dancing on my own (dancing on my own)
I make the moves up as I go (moves up as I go)
And that's what they don't know, mm, mm
That’s what they don’t know, mm, mm
"""

postchorus = """
Shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off
"""

interlude = """
Hey, hey, hey
Just think while you've been getting down and out about the liars
And the dirty, dirty cheats of the world
You could've been getting down
To this sick beat
"""

bridge = """
My ex-man brought his new girlfriend
She's like, “Oh my God,” but I'm just gonna shake
And to the fella over there with the hella good hair
Won't you come on over, baby?
We can shake, shake, shake
Yeah, oh, oh, oh
"""

With those parts, the variables verse1, verse2, prechorus, chorus, postchorus, interlude, and bridge we can see how “Shake it off” is structured (and also represent it with a lot less typing).

shakeItOffReconstructed = (verse1 +   # (it's ok to stretch expressions over several lines. It can help readabiilty)
                          prechorus + 
                          chorus + 
                          verse2 + 
                          prechorus + 
                          chorus + 
                          postchorus + 
                          interlude + 
                          bridge + 
                          chorus + 
                          postchorus * 3 )  # repeats three times
#print( shakeItOffReconstructed )

Is it really that simple? Let’s test and see if these strings are the same.

shakeItOffComplete == shakeItOffReconstructed

Verse-level representation

That’s some nice compression, but we can do better. There is a lot of repetition all over the song that we can capture in variables and chunk down. For example, the “mm, mm”s and “That’s what people say”‘s in the verses could be chunked down. But the most redundancy in any pop song is going to be the in the chorus and, in this song, especially the post-chorus. Let’s see if we can rewrite them into a more compact form.

### Original
chorus = """
'Cause the players gonna play, play, play, play, play, 
And the haters gonna hate, hate, hate, hate, hate, 
Baby, I'm just gonna shake, shake, shake, shake, shake, 
I shake it off, I shake it off
Heartbreakers gonna break, break, break, break, break, 
And the fakers gonna fake, fake, fake, fake, fake, 
Baby, I'm just gonna shake, shake, shake, shake, shake, 
I shake it off, I shake it off
"""

### Refrain
shk = 'I shake it off'   # this gets used a lot, so it gets a variable 

### Replacement
chorusReconstructed = """
'Cause the players gonna {}
And the haters gonna {}
Baby, I'm just gonna {}
{}, {}
Heartbreakers gonna {}
And the fakers gonna {}
Baby, I'm just gonna {}
{}, {}
""".format('play, ' * 5, 
           'hate, ' * 5, 
           'shake, ' * 5, 
           shk, shk, 
           'break, ' * 5, 
           'fake, ' * 5, 
           'shake, ' * 5, 
           shk, shk)

#print( chorusReconstructed)

### Test for success
chorus == chorusReconstructed

The new chorus is identical content, typed in about half as many characters. That means that, in some sense, about half the chorus of “Shake it off” is redundant.

How about the post-chorus? We’ve already defined placeholder variable shk, which it looks like we’ll keep using.

### Original
postchorus = """
Shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off
I, I, I shake it off, I shake it off
"""

### Replacement
postchorusReconstructed = """
Shake it off, {}
I, I, {}, {}
I, I, {}, {}
I, I, {}, {}
""".format( shk, shk, shk, shk, shk, shk, shk )

### Better replacement
###    (observe above that most shk's are repeated twice.
###     We can use that to get a bit more compression)
shk2 = shk + ', ' + shk
postchorusReconstructed2 = """
Shake it off, {}
I, I, {}
I, I, {}
I, I, {}
""".format( shk, shk2, shk2, shk2 )

### Even better replacement
###    (observe above that most shk's are preceded by a comma and a space.
###     We can use that too)
cshk = ', ' + shk   
shk2 = cshk + cshk
postchorusReconstructed3 = """
Shake it off{}
I, I{}
I, I{}
I, I{}
""".format( cshk, shk2, shk2, shk2 )


### Too far?
i = 'I, I'
shk2 = i + cshk + cshk
postchorusReconstructed3 = """
Shake it off{}
{}
{}
{}
""".format( cshk, shk2, shk2, shk2 )

### Test for success
postchorus == postchorusReconstructed3

We’ve reduced postchorus to almost a third of the size. Add to that that the final post-chorus of the song repeats postchorus three times, and that’s a total reduction of about nine times. In other words, from an informational standpoint, the last 20% of the song is 90% redundant.

### The last 20% (40 seconds) of "Shake it off" in one line
###  "\n" is the character representation of the line break/return key
print( ( ('Shake it off' + cshk + ('\n' + shk2) * 3 ) + '\n' ) * 3 )

Of course, it’s really not fair to evaluate music from an informational standpoint. There are other standpoints that make sense for music. Nevertheless this exercise does do something useful for us. Breaking a thing down into parts—”ana-lysis”—teaches us about a thing by showing us its natural faultlines, and revealing the formula behind it. And it’s just that kind of breaking-things-down that programming makes you good at.

If you end up analyzing another song this way, let me know!

FYI, this is an excerpt from a lesson out of my Python course at UC Davis and on Coursera.


Your face’s facets

In this project a collection of kaleidoscopic passport photos helps us reveal the subtle asymmetries in anyone’s face. The dual portraits are made from symmetrizing the left and ride halves of each face. Here are about 150 portraits from 100 people. In every photo, the leftmost portrait is the left side of the original photo (and therefore the right side of your face looking out from your own perspective).

And as you browse, consider: do you believe the conventional wisdom that the more symmetric faces are the more beautiful (whether conventionally or unconventionally)?

Pictures are below but the link to a better gallery is
https://0w.uk/facefacets.

If you’re in the collection and wish your picture taken down, let me know. The code for automating much of this is
https://github.com/enfascination/faceFacets

By
Seth Frey (enfascination.com) and Gyorgy Feher (g.feher.0@gmail.com)

About

This entry was posted on Wednesday, August 23rd, 2023 and is filed under audio/visual, code.


Definitions that rhyme

I wrote a program for finding pairs of dictionary definitions that are secretly couplets! My rhyme detector is a little rhyme-happy (“surfaces” and “foods”?), but overall I’m very pleased. Best so far:

hoary (adj.):
grayish white.
old and trite.

and

crusade (v.):
be an advocate for.
fight a holy war.

About

This entry was posted on Friday, August 4th, 2023 and is filed under code, life and words.


Simple heuristic for breaking pills in half


Quickly:
I have to give my son dramamine on road trips, but only half a pill. That’s been a bit tricky. Even scored pills don’t always break cleanly, and then what do you do? Break it again? Actually yes. I did a simple simulation to show how you can increase your chances of breaking a pill into two half-sized portions by 15-20% (e.g. from 70% to about 85%):
1. Try to break the pill in half.
2. If you succeed, great, if not, try to break each half in half.
3. Between your four resulting fragments, some pair of them has its own probability of adding up to half a pill, plus or minus.

Honestly I thought it would work better. This is the value of modeling.

Explanation:
If after a bad break from one to two pieces you break again to four pieces, you will end up with six possible combinations of the four fragments. Some of these are equivalent so all together going to four pieces amounts to creating two more chances to create a combination that adds to 50%. And it works: your chances go up. This is simple and effective. But not incredibly effective. I thought it would increase your chances of a match by 50% or more, but the benefit is closer to 15-20%. So it’s worth doing, but not a solution to the problem. Of course, after a second round of splitting you can keep splitting and continue the gambit. In the limit, you’ve reduced the pill to a powder whose grains can add to precisely 50% in uncountable numbers of combinations, but that’s a bit unwieldy for road trip dramamine. For the record, pill splitters are also too unwieldy for a roadtrip, but maybe they’re worth considering if my heuristic only provides a marginal improvement.

The code:
Here is the simulation. Parameters: I allowed anything within 10% of half of a pill to be “close enough”, so anything in the 40% to 60% range counts. Intention and skill make the distribution of splits non-uniform, so I used a truncated normal with standard deviation set to a 70% chance of splitting the pill well on the first try.

#install.packages("truncnorm")
library(truncnorm)
inc_1st <- 0
inc_2nd <- 0
tol <- 0.1
for (i in 1:100 ) {
  #print(i);
  #a <- runif(1)
  a <- rtruncnorm(1, a=0, b=1, mean=0.5, sd=0.5^3.3)
  b <- 1 - a
  if ( a > (0.5 - tol) & a < (0.5 + tol)) {
    inc_1st <- inc_1st + 1
  } else {
    #aa <- runif(1, 0, a)
    aa <- rtruncnorm(1, a=0, b=a, mean=a/2, sd=(a*2)^3.3)
    ab <- a - aa
    #ba <- runif(1, 0, b)
    ba <- rtruncnorm(1, a=0, b=b, mean=b/2, sd=(b*2)^3.3)
    bb <- b - ba
    totals <- c(aa+ba, aa+bb)
    if (any( totals > (0.5 - tol) & totals < (0.5 + tol)) ) {
      #print(totals)
      inc_2nd <- inc_2nd + 1
    } else {
      #print(totals)
    }
  }
}

#if you only have a 20% chance of getting it right with one break, you have a 50% chance by following the strategy
#if you only have a 30% chance of getting it right with one break, you have a 60% chance by following the strategy
#if you only have a 60% chance of getting it right with one break, you have a 80% chance by following the strategy
#if you only have a 70% chance of getting it right with one break, you have a 85% chance by following the strategy

print(inc_1st)
print(inc_2nd)
print(inc_1st + inc_2nd)

All the SATOR squares in English, with code

OLYMPUS DIGITAL CAMERA

Using code for recreational word play is very fun. Having fun with a housemate we drunkenly built a list of all 4- and 5- sided SATOR squares.

These squares are special because they read the same left-to-right, top-to-bottom, and both directions in reverse. The famous one is an ancient one from Latin

SATOR
AREPO
TENET
OPERA
ROTAS

In the ancient world these were used both as magic spells and possibly the first memes. They pop up all over Western history, and into today: the movie Tenet is a reference to this square.

Here are the 70 5-letter squares:

‘assam’, ‘shama’, ‘sagas’, ‘amahs’, ‘massa’
‘assam’, ‘shama’, ‘samas’, ‘amahs’, ‘massa’
‘assam’, ‘shaya’, ‘sagas’, ‘ayahs’, ‘massa’
‘assam’, ‘shaya’, ‘samas’, ‘ayahs’, ‘massa’
‘asses’, ‘slive’, ‘simis’, ‘evils’, ‘sessa’
‘asses’, ‘slive’, ‘siris’, ‘evils’, ‘sessa’
‘asses’, ‘state’, ‘sagas’, ‘etats’, ‘sessa’
‘asses’, ‘state’, ‘samas’, ‘etats’, ‘sessa’
‘asses’, ‘stime’, ‘simis’, ’emits’, ‘sessa’
‘asses’, ‘stime’, ‘siris’, ’emits’, ‘sessa’
‘asses’, ‘swone’, ‘solos’, ‘enows’, ‘sessa’
‘ayahs’, ‘yrneh’, ‘anana’, ‘henry’, ‘shaya’
‘cares’, ‘amene’, ‘refer’, ‘enema’, ‘serac’
‘dedal’, ‘enema’, ‘deked’, ‘amene’, ‘laded’
‘dedal’, ‘enema’, ‘deled’, ‘amene’, ‘laded’
‘dedal’, ‘enema’, ‘dered’, ‘amene’, ‘laded’
‘dedal’, ‘enema’, ‘dewed’, ‘amene’, ‘laded’
‘derat’, ‘enema’, ‘refer’, ‘amene’, ‘tared’
‘gater’, ‘amene’, ‘tenet’, ‘enema’, ‘retag’
‘gnats’, ‘nonet’, ‘anana’, ‘tenon’, ‘stang’
‘hales’, ‘amene’, ‘lemel’, ‘enema’, ‘selah’
‘hales’, ‘amene’, ‘level’, ‘enema’, ‘selah’
‘laded’, ‘amene’, ‘deked’, ‘enema’, ‘dedal’
‘laded’, ‘amene’, ‘deled’, ‘enema’, ‘dedal’
‘laded’, ‘amene’, ‘dered’, ‘enema’, ‘dedal’
‘laded’, ‘amene’, ‘dewed’, ‘enema’, ‘dedal’
‘lares’, ‘amene’, ‘refer’, ‘enema’, ‘seral’
‘massa’, ‘amahs’, ‘sagas’, ‘shama’, ‘assam’
‘massa’, ‘amahs’, ‘samas’, ‘shama’, ‘assam’
‘massa’, ‘ayahs’, ‘sagas’, ‘shaya’, ‘assam’
‘massa’, ‘ayahs’, ‘samas’, ‘shaya’, ‘assam’
‘pelas’, ‘enema’, ‘lemel’, ‘amene’, ‘salep’
‘pelas’, ‘enema’, ‘level’, ‘amene’, ‘salep’
‘resat’, ‘enema’, ‘sedes’, ‘amene’, ‘taser’
‘resat’, ‘enema’, ‘seles’, ‘amene’, ‘taser’
‘resat’, ‘enema’, ‘semes’, ‘amene’, ‘taser’
‘resat’, ‘enema’, ‘seres’, ‘amene’, ‘taser’
‘resat’, ‘enema’, ‘sexes’, ‘amene’, ‘taser’
‘retag’, ‘enema’, ‘tenet’, ‘amene’, ‘gater’
‘salep’, ‘amene’, ‘lemel’, ‘enema’, ‘pelas’
‘salep’, ‘amene’, ‘level’, ‘enema’, ‘pelas’
‘selah’, ‘enema’, ‘lemel’, ‘amene’, ‘hales’
‘selah’, ‘enema’, ‘level’, ‘amene’, ‘hales’
‘serac’, ‘enema’, ‘refer’, ‘amene’, ‘cares’
‘seral’, ‘enema’, ‘refer’, ‘amene’, ‘lares’
‘sesey’, ‘edile’, ‘simis’, ‘elide’, ‘yeses’
‘sesey’, ‘edile’, ‘siris’, ‘elide’, ‘yeses’
‘sesey’, ‘elide’, ‘simis’, ‘edile’, ‘yeses’
‘sesey’, ‘elide’, ‘siris’, ‘edile’, ‘yeses’
‘sessa’, ’emits’, ‘simis’, ‘stime’, ‘asses’
‘sessa’, ’emits’, ‘siris’, ‘stime’, ‘asses’
‘sessa’, ‘enows’, ‘solos’, ‘swone’, ‘asses’
‘sessa’, ‘etats’, ‘sagas’, ‘state’, ‘asses’
‘sessa’, ‘etats’, ‘samas’, ‘state’, ‘asses’
‘sessa’, ‘evils’, ‘simis’, ‘slive’, ‘asses’
‘sessa’, ‘evils’, ‘siris’, ‘slive’, ‘asses’
‘shaya’, ‘henry’, ‘anana’, ‘yrneh’, ‘ayahs’
‘stang’, ‘tenon’, ‘anana’, ‘nonet’, ‘gnats’
‘start’, ’tiler’, ‘alula’, ‘relit’, ‘trats’
‘tared’, ‘amene’, ‘refer’, ‘enema’, ‘derat’
‘taser’, ‘amene’, ‘sedes’, ‘enema’, ‘resat’
‘taser’, ‘amene’, ‘seles’, ‘enema’, ‘resat’
‘taser’, ‘amene’, ‘semes’, ‘enema’, ‘resat’
‘taser’, ‘amene’, ‘seres’, ‘enema’, ‘resat’
‘taser’, ‘amene’, ‘sexes’, ‘enema’, ‘resat’
‘trats’, ‘relit’, ‘alula’, ’tiler’, ‘start’
‘yeses’, ‘edile’, ‘simis’, ‘elide’, ‘sesey’
‘yeses’, ‘edile’, ‘siris’, ‘elide’, ‘sesey’
‘yeses’, ‘elide’, ‘simis’, ‘edile’, ‘sesey’
‘yeses’, ‘elide’, ‘siris’, ‘edile’, ‘sesey’

To generate them yourself (and the fours), here is code that you can run by pressing Play.
https://colab.research.google.com/drive/14gaONdrLuxc3Pzz6M8y1RWpTLIdLY7H0?usp=sharing
For words we used the official Scrabble list. The tests are hard to read but they check the symmetries of the square.

The interesting findings are that

  • there are 70 5-letter ones in English,
  • 494 in 4 letters,
  • none use only familiar words,
  • few make technically readable sentences,
  • we did surprisingly well building 4-letter ones by hand without the help of code, but
  • building 5 letter ones by hand is very very hard
  • They are counter-intuitive and having code made it a lot easier to think about them and understand the constraints they have to satisfy

The basic rules in building them are that

  • all n words have to be n letters long,
  • each should be reversible (form a word in both directions),
    • if there is a middle word (for 3- and 5- and other odd lengths), it should be a palindrome (e.g. “TENET”; palindromes are a special case of reversible words), and
  • at least one should begin with a vowel
    • in english the only vowels that appeared in legal 5-letter vowel-ended words in our SATOR squares were a and e, with a’s accounting for the majority.

The next challenge would be to build a SATOR cube (filled or hollow—n slices of cube or a cube with one square on each face). Probably there are none in 5 letters (if there are any, I’d guess there is just one), a couple in 4 letters, and several in 3 letters, with filled obviously more rare than hollow.

Another challenge would be to find words that I want to include that aren’t on the Scrabble list and see if they change anything.