We are going to use the Quanteda R package. If you need to, run this command to install quanteda:

#install.packages("quanteda", dependencies = TRUE)

Now load it:

library(quanteda)
Package version: 1.3.14
Parallel computing: 2 of 8 threads used.
See https://quanteda.io for tutorials and examples.

Attaching package: ‘quanteda’

The following object is masked from ‘package:utils’:

    View

Lets again load in the corpus of presidential inaugural addresses and see what it looks like:

corp <- quanteda::data_corpus_inaugural
summary(corp)
Corpus consisting of 58 documents:

            Text Types Tokens Sentences Year  President       FirstName
 1789-Washington   625   1538        23 1789 Washington          George
 1793-Washington    96    147         4 1793 Washington          George
      1797-Adams   826   2578        37 1797      Adams            John
  1801-Jefferson   717   1927        41 1801  Jefferson          Thomas
  1805-Jefferson   804   2381        45 1805  Jefferson          Thomas
    1809-Madison   535   1263        21 1809    Madison           James
    1813-Madison   541   1302        33 1813    Madison           James
     1817-Monroe  1040   3680       121 1817     Monroe           James
     1821-Monroe  1259   4886       129 1821     Monroe           James
      1825-Adams  1003   3152        74 1825      Adams     John Quincy
    1829-Jackson   517   1210        25 1829    Jackson          Andrew
    1833-Jackson   499   1269        29 1833    Jackson          Andrew
   1837-VanBuren  1315   4165        95 1837  Van Buren          Martin
   1841-Harrison  1896   9144       210 1841   Harrison   William Henry
       1845-Polk  1334   5193       153 1845       Polk      James Knox
     1849-Taylor   496   1179        22 1849     Taylor         Zachary
     1853-Pierce  1165   3641       104 1853     Pierce        Franklin
   1857-Buchanan   945   3086        89 1857   Buchanan           James
    1861-Lincoln  1075   4006       135 1861    Lincoln         Abraham
    1865-Lincoln   360    776        26 1865    Lincoln         Abraham
      1869-Grant   485   1235        40 1869      Grant      Ulysses S.
      1873-Grant   552   1475        43 1873      Grant      Ulysses S.
      1877-Hayes   831   2716        59 1877      Hayes   Rutherford B.
   1881-Garfield  1021   3212       111 1881   Garfield        James A.
  1885-Cleveland   676   1820        44 1885  Cleveland          Grover
   1889-Harrison  1352   4722       157 1889   Harrison        Benjamin
  1893-Cleveland   821   2125        58 1893  Cleveland          Grover
   1897-McKinley  1232   4361       130 1897   McKinley         William
   1901-McKinley   854   2437       100 1901   McKinley         William
  1905-Roosevelt   404   1079        33 1905  Roosevelt        Theodore
       1909-Taft  1437   5822       159 1909       Taft  William Howard
     1913-Wilson   658   1882        68 1913     Wilson         Woodrow
     1917-Wilson   549   1656        59 1917     Wilson         Woodrow
    1921-Harding  1169   3721       148 1921    Harding       Warren G.
   1925-Coolidge  1220   4440       196 1925   Coolidge          Calvin
     1929-Hoover  1090   3865       158 1929     Hoover         Herbert
  1933-Roosevelt   743   2062        85 1933  Roosevelt     Franklin D.
  1937-Roosevelt   725   1997        96 1937  Roosevelt     Franklin D.
  1941-Roosevelt   526   1544        68 1941  Roosevelt     Franklin D.
  1945-Roosevelt   275    647        26 1945  Roosevelt     Franklin D.
     1949-Truman   781   2513       116 1949     Truman        Harry S.
 1953-Eisenhower   900   2757       119 1953 Eisenhower       Dwight D.
 1957-Eisenhower   621   1931        92 1957 Eisenhower       Dwight D.
    1961-Kennedy   566   1566        52 1961    Kennedy         John F.
    1965-Johnson   568   1723        93 1965    Johnson   Lyndon Baines
      1969-Nixon   743   2437       103 1969      Nixon Richard Milhous
      1973-Nixon   544   2012        68 1973      Nixon Richard Milhous
     1977-Carter   527   1376        52 1977     Carter           Jimmy
     1981-Reagan   902   2790       128 1981     Reagan          Ronald
     1985-Reagan   925   2921       123 1985     Reagan          Ronald
       1989-Bush   795   2681       141 1989       Bush          George
    1993-Clinton   642   1833        81 1993    Clinton            Bill
    1997-Clinton   773   2449       111 1997    Clinton            Bill
       2001-Bush   621   1808        97 2001       Bush       George W.
       2005-Bush   773   2319       100 2005       Bush       George W.
      2009-Obama   938   2711       110 2009      Obama          Barack
      2013-Obama   814   2317        88 2013      Obama          Barack
      2017-Trump   582   1660        88 2017      Trump       Donald J.

Source: Gerhard Peters and John T. Woolley. The American Presidency Project.
Created: Tue Jun 13 14:51:47 2017
Notes: http://www.presidency.ucsb.edu/inaugurals.php

As a reminder, we previously used quanteda’s dfm command to generate a document-term matrix from this corpus, e.g.:

dtm <- quanteda::dfm(corp,
                                 tolower = TRUE,    # casefold
                                 stem = FALSE,                 # do not stem
                                 remove_punct = TRUE,          # remove punctuation
                                 remove = stopwords("english"), # ignore common words on a "stop" list
                                 ngrams = 1)                   # count unigrams

For illustration purposes, we’re going to create a second copy of the corpus and apply Lexicoder’s recommended preprocessing scripts to it:

#source("http://www.lexicoder.com/docs/LSDprep_jan2018.R")
source("LSDprep_jan2018.R")
corp.pp <- corp
texts.pp <- texts(corp)
texts.pp <- LSDprep_contr(texts.pp)  #expands contractions
texts.pp <- LSDprep_dict_punct(texts.pp) # Removes misleading words that have markers in punctuation
texts.pp <- remove_punctuation_from_acronyms(texts.pp) #
texts.pp <- remove_punctuation_from_abbreviations(texts.pp)
texts.pp <- mark_proper_nouns(texts.pp)
texts.pp <- LSDprep_punctspace(texts.pp) # put spaces around punctuation
texts.pp <- LSDprep_negation(texts.pp) # normalizes negations, e.g "not very" -> not
texts.pp <- LSDprep_dict(texts.pp) # Alters misleading words e.g "may very xwell"
texts(corp.pp) <- texts.pp
dfm.lsd <- dfm(corp, 
                    tolower = TRUE,    # casefold
                    stem = FALSE,                 # do not stem
                    remove_punct = TRUE,          # remove punctuation
                    #remove = stopwords("english"), # ignore common words on a "stop" list
                    #ngrams = 1
                    dictionary = data_dictionary_LSD2015)
dfm.lsd.pp <- dfm(corp.pp, 
                    tolower = TRUE,    # casefold
                    stem = FALSE,                 # do not stem
                    remove_punct = TRUE,          # remove punctuation
                    #remove = stopwords("english"), # ignore common words on a "stop" list
                    #ngrams = 1
                    dictionary = data_dictionary_LSD2015)
dfmat.lsd <- as.matrix(dfm.lsd)
dfmat.lsd.pp <- as.matrix(dfm.lsd.pp)
lsd.df <- data.frame(cbind(dfmat.lsd,dfmat.lsd.pp))
names(lsd.df) <- c("neg","pos","neg_pos","neg_neg","neg.pp","pos.pp","neg_pos.pp", "neg_neg.pp")
rownames(lsd.df) <- docnames(corp) 
lsd.df$adj_pos <- lsd.df$pos - lsd.df$neg_pos
lsd.df$adj_neg <- lsd.df$neg - lsd.df$neg_neg
lsd.df$adj_pos.pp <- lsd.df$pos.pp - lsd.df$neg_pos.pp
lsd.df$adj_neg.pp <- lsd.df$neg.pp - lsd.df$neg_neg.pp
summary(lsd.df)
      neg              pos           neg_pos         neg_neg     
 Min.   :  3.00   Min.   : 10.0   Min.   :0.000   Min.   :0.000  
 1st Qu.: 52.25   1st Qu.:122.2   1st Qu.:0.000   1st Qu.:0.000  
 Median : 77.00   Median :176.0   Median :1.000   Median :1.000  
 Mean   : 86.95   Mean   :199.6   Mean   :1.052   Mean   :1.707  
 3rd Qu.:117.75   3rd Qu.:246.5   3rd Qu.:2.000   3rd Qu.:2.000  
 Max.   :290.00   Max.   :571.0   Max.   :5.000   Max.   :8.000  
     neg.pp           pos.pp        neg_pos.pp       neg_neg.pp    
 Min.   :  3.00   Min.   : 10.0   Min.   : 0.000   Min.   : 0.000  
 1st Qu.: 51.25   1st Qu.:116.0   1st Qu.: 2.000   1st Qu.: 2.000  
 Median : 76.50   Median :171.5   Median : 4.000   Median : 5.000  
 Mean   : 85.97   Mean   :192.0   Mean   : 4.828   Mean   : 5.828  
 3rd Qu.:117.50   3rd Qu.:237.5   3rd Qu.: 6.000   3rd Qu.: 8.000  
 Max.   :288.00   Max.   :540.0   Max.   :20.000   Max.   :22.000  
    adj_pos         adj_neg         adj_pos.pp      adj_neg.pp    
 Min.   : 10.0   Min.   :  3.00   Min.   : 10.0   Min.   :  3.00  
 1st Qu.:122.2   1st Qu.: 52.25   1st Qu.:114.8   1st Qu.: 47.50  
 Median :175.0   Median : 74.50   Median :168.0   Median : 69.50  
 Mean   :198.5   Mean   : 85.24   Mean   :187.2   Mean   : 80.14  
 3rd Qu.:246.5   3rd Qu.:116.00   3rd Qu.:236.8   3rd Qu.:108.75  
 Max.   :566.0   Max.   :289.00   Max.   :520.0   Max.   :270.00  
plot(c(1789,2017),c(0,600),type="n", main="Raw Dictionary Counts", xlab="Year", ylab = "Count")
lines(docvars(corp)$Year,lsd.df$pos, col="blue", lty=1)
lines(docvars(corp)$Year,lsd.df$pos.pp, col="blue", lty=2)
lines(docvars(corp)$Year,lsd.df$adj_pos, col="blue", lty=3)
lines(docvars(corp)$Year,lsd.df$adj_pos.pp, col="blue", lty=4)
lines(docvars(corp)$Year,lsd.df$neg, col="red", lty=1)
lines(docvars(corp)$Year,lsd.df$neg.pp, col="red", lty=2)
lines(docvars(corp)$Year,lsd.df$adj_neg, col="red", lty=3)
lines(docvars(corp)$Year,lsd.df$adj_neg.pp, col="red", lty=4)

In this corpus, then, it makes virtually no difference whether you apply the recommended preprocessing scripts, account for negations, or both. The dictionary counts are correlated at .997 or higher. So, for the remainder of this example, we will keep it simple and use the unadjusted counts from the texts without the preprocessing scripts (pos and neg).

cor(lsd.df[,c('pos','adj_pos','pos.pp','adj_pos.pp')])
                 pos   adj_pos    pos.pp adj_pos.pp
pos        1.0000000 0.9999388 0.9990616  0.9984557
adj_pos    0.9999388 1.0000000 0.9989778  0.9985275
pos.pp     0.9990616 0.9989778 1.0000000  0.9995618
adj_pos.pp 0.9984557 0.9985275 0.9995618  1.0000000
cor(lsd.df[,c('neg','adj_neg','neg.pp','adj_neg.pp')])
                 neg   adj_neg    neg.pp adj_neg.pp
neg        1.0000000 0.9995111 0.9993675  0.9974758
adj_neg    0.9995111 1.0000000 0.9987653  0.9982477
neg.pp     0.9993675 0.9987653 1.0000000  0.9980181
adj_neg.pp 0.9974758 0.9982477 0.9980181  1.0000000

So, our first possible measure of positive sentiment is something like the counts of positive tokens. That indicates that the most positive speech was that of William Henry Harrison in 1841. Harrison died 31 days into his presidency.

sent.absposcount <- lsd.df$pos
names(sent.absposcount) <- docnames(corp)
sort(sent.absposcount, dec=T)[1:10]
1841-Harrison     1845-Polk 1897-McKinley 1837-VanBuren 1889-Harrison 
          571           388           375           354           354 
    1909-Taft 1925-Coolidge   1821-Monroe   1817-Monroe   1853-Pierce 
          353           350           344           341           333 

Conversely, using absolute negative counts suggests that the most negative speech was also that of William Henry Harrison.

sent.absnegcount <- lsd.df$neg
names(sent.absnegcount) <- docnames(corp)
sort(sent.absnegcount, dec=T)[1:10]
1841-Harrison 1837-VanBuren     1845-Polk  1921-Harding     1909-Taft 
          290           183           174           168           167 
 1861-Lincoln 1889-Harrison   1821-Monroe   1929-Hoover 1925-Coolidge 
          160           157           150           139           127 

Of course, Harrison’s was the longest inaugural speech. A longer speech has more positive and negative and neutral tokens, all else equal. Of course, this means the positive and negative counts are also highly correlated.

lsd.df$tot_affect <- lsd.df$pos + lsd.df$neg
lsd.df$tot_tokens <- rowSums(dtm)
cor(lsd.df[,c('pos','neg','tot_affect','tot_tokens')])
                 pos       neg tot_affect tot_tokens
pos        1.0000000 0.8913067  0.9885632  0.9529750
neg        0.8913067 1.0000000  0.9494891  0.9355812
tot_affect 0.9885632 0.9494891  1.0000000  0.9707433
tot_tokens 0.9529750 0.9355812  0.9707433  1.0000000

We tend to think of positive and negative affect as on the same scale, so perhaps we can just use the absolute difference as a measure of sentiment?

sent_absdiff <- lsd.df$pos - lsd.df$neg
names(sent_absdiff) <- docnames(corp)
sort(sent_absdiff, dec=T)[1:10] # Most positive?
1841-Harrison 1897-McKinley 1925-Coolidge   1817-Monroe   1853-Pierce 
          281           257           223           217           217 
    1845-Polk   1949-Truman 1889-Harrison   1821-Monroe     1909-Taft 
          214           199           197           194           186 
sort(sent_absdiff, dec=F)[1:10] # Most negative?
   1865-Lincoln 1793-Washington    1813-Madison    1965-Johnson 
             -6               7              18              31 
   1961-Kennedy  1945-Roosevelt  1905-Roosevelt    1993-Clinton 
             33              35              41              48 
   1861-Lincoln  1941-Roosevelt 
             49              55 

Harrison again. At least he’s not the most positive and the most negative. But the shortest speech, Washington’s second inaugural at just 135 tokens, is the second most negative? And the most negative is Lincoln’s second inaugural, the third shortest?

Length is having a couple of effects here. The most obvious is that the base rates for positive tokens and negative tokens are different – inaugurals are more positive than negative, which makes sense – so the longer the speech is, the greater is the likely difference in positive and negative counts:

plot(lsd.df$tot_tokens,sent_absdiff,pch=19, col=rgb(0,0,0,.5), log="x",main="Sentiment Measured by Absolute Difference in Counts", xlab = "Total Tokens", ylab = "Sentiment")

So this leads us to the first constructed measure of sentiment actually recommended by Soroka and Young. The fraction (percentage would be the same x100) of affect tokens that are positive minus the fraction of affect tokens that are negative.

lsd.df$posfrac <- lsd.df$pos/lsd.df$tot_affect
lsd.df$negfrac <- lsd.df$neg/lsd.df$tot_affect
sent_fracdiff <- lsd.df$posfrac - lsd.df$negfrac
names(sent_fracdiff) <- docnames(corp)
sort(sent_fracdiff, dec=T)[1:10]
    1849-Taylor      1797-Adams     1977-Carter    1829-Jackson 
      0.7315436       0.5933333       0.5612245       0.5602837 
    1949-Truman       1989-Bush      1877-Hayes  1885-Cleveland 
      0.5574230       0.5529412       0.5516014       0.5438596 
1793-Washington      1973-Nixon 
      0.5384615       0.5371179 
sort(sent_fracdiff, dec=F)[1:10]
  1865-Lincoln   1813-Madison   1861-Lincoln   1961-Kennedy 
   -0.07500000     0.09278351     0.13279133     0.16582915 
  1965-Johnson     2009-Obama 1805-Jefferson   1993-Clinton 
    0.20000000     0.23717949     0.26640927     0.27586207 
1893-Cleveland    1913-Wilson 
    0.27976190     0.28301887 
summary(sent_fracdiff)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-0.0750  0.3225  0.4052  0.3971  0.4779  0.7315 
mn.sent_fracdiff <- mean(sent_fracdiff)
plot(docvars(corp)$Year,sent_fracdiff, type="l", main="Sentiment by Fraction Difference", xlab="Year", ylab = "Sentiment", ylim=c(-1,1))
lines(c(1700,3000),c(0,0), col="gray")
lines(c(1700,3000),c(mn.sent_fracdiff,mn.sent_fracdiff), col="gray", lty=2)

(Note that since affect tokens = positive tokens + negative tokens, this is functionally equivalent to just the fraction of affect tokens that are positive. The former is centered on 0 and runs from -1 to 1; this is centered at .5 and runs from 0 to 1. They are correlated, by definition, at +1)

sent_frac <- lsd.df$posfrac
names(sent_frac) <- docnames(corp)
sort(sent_frac, dec=T)[1:10]
    1849-Taylor      1797-Adams     1977-Carter    1829-Jackson 
      0.8657718       0.7966667       0.7806122       0.7801418 
    1949-Truman       1989-Bush      1877-Hayes  1885-Cleveland 
      0.7787115       0.7764706       0.7758007       0.7719298 
1793-Washington      1973-Nixon 
      0.7692308       0.7685590 
sort(sent_frac, dec=F)[1:10]
  1865-Lincoln   1813-Madison   1861-Lincoln   1961-Kennedy 
     0.4625000      0.5463918      0.5663957      0.5829146 
  1965-Johnson     2009-Obama 1805-Jefferson   1993-Clinton 
     0.6000000      0.6185897      0.6332046      0.6379310 
1893-Cleveland    1913-Wilson 
     0.6398810      0.6415094 
summary(sent_frac)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.4625  0.6613  0.7026  0.6986  0.7390  0.8658 
mn.sent_frac <- mean(sent_frac)
plot(docvars(corp)$Year,sent_frac, type="l", main="Sentiment by Fraction", xlab="Year", ylab = "Sentiment", ylim=c(0,1))
lines(c(1700,3000),c(0.5,0.5), col="gray")
lines(c(1700,3000),c(mn.sent_frac,mn.sent_frac), col="gray", lty=2)

plot(lsd.df$tot_tokens,sent_fracdiff,pch=19, col=rgb(0,0,0,.5), ylim = c(-1,1), log="x",main="Sentiment Measured by Fraction Difference", xlab = "Total Tokens", ylab = "Sentiment")
lines(c(1,10000),c(0,0), col="gray")
lines(c(1,10000),c(mn.sent_fracdiff,mn.sent_fracdiff), col="gray", lty=2)

Or we can start down the Fightin Words logical path and look at the logratio:

sent_logratio <- log(lsd.df$pos+1) - log(lsd.df$neg +1)
names(sent_logratio) <- docnames(corp)
sort(sent_logratio, dec=T)[1:10]
   1849-Taylor     1797-Adams    1977-Carter    1949-Truman 
      1.823012       1.353505       1.252763       1.249185 
  1829-Jackson      1989-Bush     1877-Hayes 1885-Cleveland 
      1.243794       1.232862       1.230189       1.205858 
    1973-Nixon  1897-McKinley 
      1.187166       1.150466 
sort(sent_logratio, dec=F)[1:10]
  1865-Lincoln   1813-Madison   1861-Lincoln   1961-Kennedy 
    -0.1466035      0.1841925      0.2657032      0.3313571 
  1965-Johnson     2009-Obama 1805-Jefferson   1993-Clinton 
     0.4001601      0.4803664      0.5415973      0.5596158 
1893-Cleveland    1913-Wilson 
     0.5712574      0.5761755 
summary(sent_logratio)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-0.1466  0.6663  0.8517  0.8479  1.0137  1.8230 
mn.sent_logratio <- mean(sent_logratio)
plot(docvars(corp)$Year,sent_logratio, type="l", main="Sentiment by Logratio", xlab="Year", ylab = "Sentiment", ylim=c(-2,2))
lines(c(1700,3000),c(0,0), col="gray")
lines(c(1700,3000),c(mn.sent_logratio,mn.sent_logratio), col="gray", lty=2)

The logratio measure is correlated with the fraction measure at .99; there are more substantial differences when the fractions involved are more extreme.

An advantage of the logratio is that we can remove the base rates if that’s a desirable thing to do.

tot_pos_count <- sum(lsd.df$pos+1)
tot_neg_count <- sum(lsd.df$neg+1)
sent_rellogratio <- log(lsd.df$pos+1) - log(tot_pos_count) - (log(lsd.df$neg +1) - log(tot_neg_count))
names(sent_rellogratio) <- docnames(corp)
sort(sent_rellogratio, dec=T)[1:10]
   1849-Taylor     1797-Adams    1977-Carter    1949-Truman 
     0.9986027      0.5290953      0.4283537      0.4247759 
  1829-Jackson      1989-Bush     1877-Hayes 1885-Cleveland 
     0.4193850      0.4084525      0.4057794      0.3814485 
    1973-Nixon  1897-McKinley 
     0.3627564      0.3260564 
sort(sent_rellogratio, dec=F)[1:10]
  1865-Lincoln   1813-Madison   1861-Lincoln   1961-Kennedy 
    -0.9710128     -0.6402168     -0.5587061     -0.4930522 
  1965-Johnson     2009-Obama 1805-Jefferson   1993-Clinton 
    -0.4242492     -0.3440429     -0.2828120     -0.2647935 
1893-Cleveland    1913-Wilson 
    -0.2531519     -0.2482338 
summary(sent_rellogratio)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
-0.97101 -0.15812  0.02727  0.02344  0.18925  0.99860 
mn.sent_rellogratio <- mean(sent_rellogratio)
plot(docvars(corp)$Year,sent_rellogratio, type="l", main="Sentiment by Relative Logratio", xlab="Year", ylab = "Sentiment", ylim=c(-1,1))
lines(c(1700,3000),c(0,0), col="gray")
lines(c(1700,3000),c(mn.sent_rellogratio,mn.sent_rellogratio), col="gray", lty=2)

A bigger advantage is the “Fightin Words” logic that allows us to correct for the heteroskedasticity arising from document length. The standard error for the log((a/b)/(c/d)) when a,b,c, and d are Poisson distributed counts is ~ sqrt(1/a + 1/b + 1/c + 1/d).

se.sent_rellogratio = sqrt(1/(lsd.df$pos+1) + 1/tot_pos_count + 1/(lsd.df$neg+1) + 1/tot_neg_count)
sent_zlogratio = sent_rellogratio / se.sent_rellogratio
summary(sent_zlogratio)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-5.2663 -1.6414  0.1917  0.0249  1.2690  4.2353 
plot(lsd.df$tot_tokens,sent_zlogratio,pch=19, col=rgb(0,0,0,.5), ylim = c(-6,6), log="x",main="Sentiment Measured by Relative Logratio Z-Score", xlab = "Total Tokens", ylab = "Sentiment")
lines(c(1,10000),c(0,0), col="gray")

#lines(c(1,10000),c(mn.sent_fracdiff,mn.sent_fracdiff), col="gray", lty=2)
                         
sort(sent_zlogratio,dec=T)[1:10]
   1849-Taylor     1797-Adams    1949-Truman  1897-McKinley 
      4.235272       3.688378       3.320362       3.061199 
    1877-Hayes      1989-Bush    1977-Carter 1885-Cleveland 
      2.835939       2.720086       2.493854       2.422218 
    1973-Nixon   1829-Jackson 
      2.319923       2.082887 
sort(sent_zlogratio,dec=F)[1:10]
  1861-Lincoln   1813-Madison   1865-Lincoln   1961-Kennedy 
     -5.266341      -4.432318      -4.372110      -3.424157 
    2009-Obama   1965-Johnson 1893-Cleveland 1805-Jefferson 
     -2.931879      -2.591838      -2.211094      -2.184591 
 1841-Harrison  1881-Garfield 
     -2.009732      -1.837473 

So … four of the five most negative (or least positive) inaugural addresses are Lincoln’s two, Kennedy’s, and Obama’s first? All of those are considered among the most inspirational, uplifting, hopeful inaugurals ever. What gives?

Maybe it’s Lexicoder. OK, let’s drop $10 and try the LIWC dictionaries …

time passes … money flows

liwc.df <- read.csv("LIWC-Inaugurals.csv", header=TRUE)
sent_liwc <- liwc.df$Tone
names(sent_liwc) <- docnames(corp)
plot(lsd.df$tot_tokens,sent_liwc,pch=19, col=rgb(0,0,0,.5), ylim = c(0,100), log="x",main="Tone Measured by LIWC", xlab = "Total Tokens", ylab = "Sentiment")
lines(c(1,10000),c(50,50), col="gray")

Somewhat similar to sent_fracdiff from Lexicoder. Correlated at .67, higher in variability for shorter documents, and in agreement that Lincoln’s and Kennedy’s inaugurals were among the most negative. LIWC seems to saturate on the positive end, making it difficult to see relative differences among the most positive.

cor(sent_liwc, sent_fracdiff) # correlated .67

sort(sent_liwc,dec=T)[1:10]
sort(sent_liwc,dec=F)[1:10]

So, again, what gives? What gives is that sentiment analysis based on dictionaries, especially dictionaries built for different contexts than the application, is often so noisy as to be effectively useless.

lincolnlines <- c("With malice toward none;", "with charity for all;","let us strive on to finish the work we are in;", "to bind up the nation’s wounds;","to care for him who shall have borne the battle, and for his widow, and his orphan—")
lincolnlines
[1] "With malice toward none;"                                                           
[2] "with charity for all;"                                                              
[3] "let us strive on to finish the work we are in;"                                     
[4] "to bind up the nation’s wounds;"                                                    
[5] "to care for him who shall have borne the battle, and for his widow, and his orphan—"
dfm(lincolnlines,
    tolower = TRUE,    # casefold
    stem = FALSE,                 # do not stem
    remove_punct = TRUE,          # remove punctuation
    dictionary = data_dictionary_LSD2015)
Document-feature matrix of: 5 documents, 4 features (75.0% sparse).
5 x 4 sparse Matrix of class "dfm"
       features
docs    negative positive neg_positive neg_negative
  text1        1        0            0            0
  text2        0        1            0            0
  text3        0        0            0            0
  text4        1        0            0            0
  text5        2        1            0            0

Consider this Kennedy line:

dfm("Let us never negotiate out of fear. But let us never fear to negotiate.",
    tolower = TRUE,    # casefold
    stem = FALSE,                 # do not stem
    remove_punct = TRUE,          # remove punctuation
    dictionary = data_dictionary_LSD2015)
Document-feature matrix of: 1 document, 4 features (75.0% sparse).
1 x 4 sparse Matrix of class "dfm"
       features
docs    negative positive neg_positive neg_negative
  text1        2        0            0            0

Or these:

jfklines <- c("The graves of young Americans who answered the call to service surround the globe.", "Now the trumpet summons us again", "-- not as a call to bear arms, though arms we need;", "not as a call to battle, though embattled we are"," -- but a call to bear the burden of a long twilight struggle, year in and year out,", '\"rejoicing in hope, patient in tribulation\"', "-- a struggle against the common enemies of man: tyranny, poverty, disease, and war itself.","Can we forge against these enemies a grand and global alliance, North and South, East and West, that can assure a more fruitful life for all mankind?")
jfklines
[1] "The graves of young Americans who answered the call to service surround the globe."                                                                   
[2] "Now the trumpet summons us again"                                                                                                                     
[3] "-- not as a call to bear arms, though arms we need;"                                                                                                  
[4] "not as a call to battle, though embattled we are"                                                                                                     
[5] " -- but a call to bear the burden of a long twilight struggle, year in and year out,"                                                                 
[6] "\"rejoicing in hope, patient in tribulation\""                                                                                                        
[7] "-- a struggle against the common enemies of man: tyranny, poverty, disease, and war itself."                                                          
[8] "Can we forge against these enemies a grand and global alliance, North and South, East and West, that can assure a more fruitful life for all mankind?"
dfm(jfklines,
    tolower = TRUE,    # casefold
    stem = FALSE,                 # do not stem
    remove_punct = TRUE,          # remove punctuation
    dictionary = data_dictionary_LSD2015)
Document-feature matrix of: 8 documents, 4 features (78.1% sparse).
8 x 4 sparse Matrix of class "dfm"
       features
docs    negative positive neg_positive neg_negative
  text1        1        0            0            0
  text2        0        0            0            0
  text3        0        0            0            0
  text4        1        0            0            0
  text5        3        0            0            0
  text6        0        2            0            0
  text7        7        0            0            0
  text8        2        4            0            0

It’s very difficult to build a dictionary that captures more signal than noise, especially across different sorts of contexts.

LS0tCnRpdGxlOiAiVGV4dCBhcyBEYXRhIFR1dG9yaWFsIC0gU2VudGltZW50IEFuYWx5c2lzIHdpdGggRGljdGlvbmFyaWVzIgphdXRob3I6ICJCdXJ0IEwuIE1vbnJvZSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogeWVzCiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgdGhlbWU6IHVuaXRlZAogICAgdG9jOiB5ZXMKLS0tCgpXZSBhcmUgZ29pbmcgdG8gdXNlIHRoZSBRdWFudGVkYSBSIHBhY2thZ2UuIElmIHlvdSBuZWVkIHRvLCBydW4gdGhpcyBjb21tYW5kIHRvIGluc3RhbGwgcXVhbnRlZGE6CgpgYGB7cn0KI2luc3RhbGwucGFja2FnZXMoInF1YW50ZWRhIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkKYGBgCgpOb3cgbG9hZCBpdDoKYGBge3J9CmxpYnJhcnkocXVhbnRlZGEpCmBgYAoKTGV0cyBhZ2FpbiBsb2FkIGluIHRoZSBjb3JwdXMgb2YgcHJlc2lkZW50aWFsIGluYXVndXJhbCBhZGRyZXNzZXMgYW5kIHNlZSB3aGF0IGl0IGxvb2tzIGxpa2U6CgpgYGB7cn0KY29ycCA8LSBxdWFudGVkYTo6ZGF0YV9jb3JwdXNfaW5hdWd1cmFsCgpzdW1tYXJ5KGNvcnApCmBgYAoKQXMgYSByZW1pbmRlciwgd2UgcHJldmlvdXNseSB1c2VkIHF1YW50ZWRhJ3MgYGRmbWAgY29tbWFuZCB0byBnZW5lcmF0ZSBhIGRvY3VtZW50LXRlcm0gbWF0cml4IGZyb20gdGhpcyBjb3JwdXMsIGUuZy46CgpgYGB7cn0KZHRtIDwtIHF1YW50ZWRhOjpkZm0oY29ycCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9sb3dlciA9IFRSVUUsICAgICMgY2FzZWZvbGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RlbSA9IEZBTFNFLCAgICAgICAgICAgICAgICAgIyBkbyBub3Qgc3RlbQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZW1vdmVfcHVuY3QgPSBUUlVFLCAgICAgICAgICAjIHJlbW92ZSBwdW5jdHVhdGlvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZW1vdmUgPSBzdG9wd29yZHMoImVuZ2xpc2giKSwgIyBpZ25vcmUgY29tbW9uIHdvcmRzIG9uIGEgInN0b3AiIGxpc3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmdyYW1zID0gMSkgICAgICAgICAgICAgICAgICAgIyBjb3VudCB1bmlncmFtcwpgYGAKCgpGb3IgaWxsdXN0cmF0aW9uIHB1cnBvc2VzLCB3ZSdyZSBnb2luZyB0byBjcmVhdGUgYSBzZWNvbmQgY29weSBvZiB0aGUgY29ycHVzIGFuZCBhcHBseSBMZXhpY29kZXIncyByZWNvbW1lbmRlZCBwcmVwcm9jZXNzaW5nIHNjcmlwdHMgdG8gaXQ6CgpgYGB7cn0KI3NvdXJjZSgiaHR0cDovL3d3dy5sZXhpY29kZXIuY29tL2RvY3MvTFNEcHJlcF9qYW4yMDE4LlIiKQpzb3VyY2UoIkxTRHByZXBfamFuMjAxOC5SIikKCmNvcnAucHAgPC0gY29ycAoKdGV4dHMucHAgPC0gdGV4dHMoY29ycCkKdGV4dHMucHAgPC0gTFNEcHJlcF9jb250cih0ZXh0cy5wcCkgICNleHBhbmRzIGNvbnRyYWN0aW9ucwp0ZXh0cy5wcCA8LSBMU0RwcmVwX2RpY3RfcHVuY3QodGV4dHMucHApICMgUmVtb3ZlcyBtaXNsZWFkaW5nIHdvcmRzIHRoYXQgaGF2ZSBtYXJrZXJzIGluIHB1bmN0dWF0aW9uCnRleHRzLnBwIDwtIHJlbW92ZV9wdW5jdHVhdGlvbl9mcm9tX2Fjcm9ueW1zKHRleHRzLnBwKSAjCnRleHRzLnBwIDwtIHJlbW92ZV9wdW5jdHVhdGlvbl9mcm9tX2FiYnJldmlhdGlvbnModGV4dHMucHApCnRleHRzLnBwIDwtIG1hcmtfcHJvcGVyX25vdW5zKHRleHRzLnBwKQp0ZXh0cy5wcCA8LSBMU0RwcmVwX3B1bmN0c3BhY2UodGV4dHMucHApICMgcHV0IHNwYWNlcyBhcm91bmQgcHVuY3R1YXRpb24KdGV4dHMucHAgPC0gTFNEcHJlcF9uZWdhdGlvbih0ZXh0cy5wcCkgIyBub3JtYWxpemVzIG5lZ2F0aW9ucywgZS5nICJub3QgdmVyeSIgLT4gbm90CnRleHRzLnBwIDwtIExTRHByZXBfZGljdCh0ZXh0cy5wcCkgIyBBbHRlcnMgbWlzbGVhZGluZyB3b3JkcyBlLmcgIm1heSB2ZXJ5IHh3ZWxsIgoKdGV4dHMoY29ycC5wcCkgPC0gdGV4dHMucHAKCmRmbS5sc2QgPC0gZGZtKGNvcnAsIAogICAgICAgICAgICAgICAgICAgIHRvbG93ZXIgPSBUUlVFLCAgICAjIGNhc2Vmb2xkCiAgICAgICAgICAgICAgICAgICAgc3RlbSA9IEZBTFNFLCAgICAgICAgICAgICAgICAgIyBkbyBub3Qgc3RlbQogICAgICAgICAgICAgICAgICAgIHJlbW92ZV9wdW5jdCA9IFRSVUUsICAgICAgICAgICMgcmVtb3ZlIHB1bmN0dWF0aW9uCiAgICAgICAgICAgICAgICAgICAgI3JlbW92ZSA9IHN0b3B3b3JkcygiZW5nbGlzaCIpLCAjIGlnbm9yZSBjb21tb24gd29yZHMgb24gYSAic3RvcCIgbGlzdAogICAgICAgICAgICAgICAgICAgICNuZ3JhbXMgPSAxCiAgICAgICAgICAgICAgICAgICAgZGljdGlvbmFyeSA9IGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1KQoKZGZtLmxzZC5wcCA8LSBkZm0oY29ycC5wcCwgCiAgICAgICAgICAgICAgICAgICAgdG9sb3dlciA9IFRSVUUsICAgICMgY2FzZWZvbGQKICAgICAgICAgICAgICAgICAgICBzdGVtID0gRkFMU0UsICAgICAgICAgICAgICAgICAjIGRvIG5vdCBzdGVtCiAgICAgICAgICAgICAgICAgICAgcmVtb3ZlX3B1bmN0ID0gVFJVRSwgICAgICAgICAgIyByZW1vdmUgcHVuY3R1YXRpb24KICAgICAgICAgICAgICAgICAgICAjcmVtb3ZlID0gc3RvcHdvcmRzKCJlbmdsaXNoIiksICMgaWdub3JlIGNvbW1vbiB3b3JkcyBvbiBhICJzdG9wIiBsaXN0CiAgICAgICAgICAgICAgICAgICAgI25ncmFtcyA9IDEKICAgICAgICAgICAgICAgICAgICBkaWN0aW9uYXJ5ID0gZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpCgpkZm1hdC5sc2QgPC0gYXMubWF0cml4KGRmbS5sc2QpCmRmbWF0LmxzZC5wcCA8LSBhcy5tYXRyaXgoZGZtLmxzZC5wcCkKCmxzZC5kZiA8LSBkYXRhLmZyYW1lKGNiaW5kKGRmbWF0LmxzZCxkZm1hdC5sc2QucHApKQpuYW1lcyhsc2QuZGYpIDwtIGMoIm5lZyIsInBvcyIsIm5lZ19wb3MiLCJuZWdfbmVnIiwibmVnLnBwIiwicG9zLnBwIiwibmVnX3Bvcy5wcCIsICJuZWdfbmVnLnBwIikKcm93bmFtZXMobHNkLmRmKSA8LSBkb2NuYW1lcyhjb3JwKSAKCmxzZC5kZiRhZGpfcG9zIDwtIGxzZC5kZiRwb3MgLSBsc2QuZGYkbmVnX3Bvcwpsc2QuZGYkYWRqX25lZyA8LSBsc2QuZGYkbmVnIC0gbHNkLmRmJG5lZ19uZWcKbHNkLmRmJGFkal9wb3MucHAgPC0gbHNkLmRmJHBvcy5wcCAtIGxzZC5kZiRuZWdfcG9zLnBwCmxzZC5kZiRhZGpfbmVnLnBwIDwtIGxzZC5kZiRuZWcucHAgLSBsc2QuZGYkbmVnX25lZy5wcAoKc3VtbWFyeShsc2QuZGYpCgpwbG90KGMoMTc4OSwyMDE3KSxjKDAsNjAwKSx0eXBlPSJuIiwgbWFpbj0iUmF3IERpY3Rpb25hcnkgQ291bnRzIiwgeGxhYj0iWWVhciIsIHlsYWIgPSAiQ291bnQiKQpsaW5lcyhkb2N2YXJzKGNvcnApJFllYXIsbHNkLmRmJHBvcywgY29sPSJibHVlIiwgbHR5PTEpCmxpbmVzKGRvY3ZhcnMoY29ycCkkWWVhcixsc2QuZGYkcG9zLnBwLCBjb2w9ImJsdWUiLCBsdHk9MikKbGluZXMoZG9jdmFycyhjb3JwKSRZZWFyLGxzZC5kZiRhZGpfcG9zLCBjb2w9ImJsdWUiLCBsdHk9MykKbGluZXMoZG9jdmFycyhjb3JwKSRZZWFyLGxzZC5kZiRhZGpfcG9zLnBwLCBjb2w9ImJsdWUiLCBsdHk9NCkKCmxpbmVzKGRvY3ZhcnMoY29ycCkkWWVhcixsc2QuZGYkbmVnLCBjb2w9InJlZCIsIGx0eT0xKQpsaW5lcyhkb2N2YXJzKGNvcnApJFllYXIsbHNkLmRmJG5lZy5wcCwgY29sPSJyZWQiLCBsdHk9MikKbGluZXMoZG9jdmFycyhjb3JwKSRZZWFyLGxzZC5kZiRhZGpfbmVnLCBjb2w9InJlZCIsIGx0eT0zKQpsaW5lcyhkb2N2YXJzKGNvcnApJFllYXIsbHNkLmRmJGFkal9uZWcucHAsIGNvbD0icmVkIiwgbHR5PTQpCmBgYAoKSW4gdGhpcyBjb3JwdXMsIHRoZW4sIGl0IG1ha2VzIHZpcnR1YWxseSBubyBkaWZmZXJlbmNlIHdoZXRoZXIgeW91IGFwcGx5IHRoZSByZWNvbW1lbmRlZCBwcmVwcm9jZXNzaW5nIHNjcmlwdHMsIGFjY291bnQgZm9yIG5lZ2F0aW9ucywgb3IgYm90aC4gVGhlIGRpY3Rpb25hcnkgY291bnRzIGFyZSBjb3JyZWxhdGVkIGF0IC45OTcgb3IgaGlnaGVyLiBTbywgZm9yIHRoZSByZW1haW5kZXIgb2YgdGhpcyBleGFtcGxlLCB3ZSB3aWxsIGtlZXAgaXQgc2ltcGxlIGFuZCB1c2UgdGhlIHVuYWRqdXN0ZWQgY291bnRzIGZyb20gdGhlIHRleHRzIHdpdGhvdXQgdGhlIHByZXByb2Nlc3Npbmcgc2NyaXB0cyAoYHBvc2AgYW5kIGBuZWdgKS4KYGBge3J9CmNvcihsc2QuZGZbLGMoJ3BvcycsJ2Fkal9wb3MnLCdwb3MucHAnLCdhZGpfcG9zLnBwJyldKQoKY29yKGxzZC5kZlssYygnbmVnJywnYWRqX25lZycsJ25lZy5wcCcsJ2Fkal9uZWcucHAnKV0pCgpgYGAKClNvLCBvdXIgZmlyc3QgcG9zc2libGUgbWVhc3VyZSBvZiBwb3NpdGl2ZSBzZW50aW1lbnQgaXMgc29tZXRoaW5nIGxpa2UgdGhlIGNvdW50cyBvZiBwb3NpdGl2ZSB0b2tlbnMuIFRoYXQgaW5kaWNhdGVzIHRoYXQgdGhlIG1vc3QgcG9zaXRpdmUgc3BlZWNoIHdhcyB0aGF0IG9mIFdpbGxpYW0gSGVucnkgSGFycmlzb24gaW4gMTg0MS4gSGFycmlzb24gZGllZCAzMSBkYXlzIGludG8gaGlzIHByZXNpZGVuY3kuCgpgYGB7cn0Kc2VudC5hYnNwb3Njb3VudCA8LSBsc2QuZGYkcG9zCm5hbWVzKHNlbnQuYWJzcG9zY291bnQpIDwtIGRvY25hbWVzKGNvcnApCnNvcnQoc2VudC5hYnNwb3Njb3VudCwgZGVjPVQpWzE6MTBdCmBgYAoKQ29udmVyc2VseSwgdXNpbmcgYWJzb2x1dGUgbmVnYXRpdmUgY291bnRzIHN1Z2dlc3RzIHRoYXQgdGhlIG1vc3QgbmVnYXRpdmUgc3BlZWNoIHdhcyBhbHNvIHRoYXQgb2YgV2lsbGlhbSBIZW5yeSBIYXJyaXNvbi4KCmBgYHtyfQpzZW50LmFic25lZ2NvdW50IDwtIGxzZC5kZiRuZWcKbmFtZXMoc2VudC5hYnNuZWdjb3VudCkgPC0gZG9jbmFtZXMoY29ycCkKc29ydChzZW50LmFic25lZ2NvdW50LCBkZWM9VClbMToxMF0KYGBgCgpPZiBjb3Vyc2UsIEhhcnJpc29uJ3Mgd2FzIHRoZSAqbG9uZ2VzdCogaW5hdWd1cmFsIHNwZWVjaC4gQSBsb25nZXIgc3BlZWNoIGhhcyBtb3JlIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBhbmQgbmV1dHJhbCB0b2tlbnMsIGFsbCBlbHNlIGVxdWFsLiBPZiBjb3Vyc2UsIHRoaXMgbWVhbnMgdGhlIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBjb3VudHMgYXJlICphbHNvKiBoaWdobHkgY29ycmVsYXRlZC4KCmBgYHtyfQpsc2QuZGYkdG90X2FmZmVjdCA8LSBsc2QuZGYkcG9zICsgbHNkLmRmJG5lZwpsc2QuZGYkdG90X3Rva2VucyA8LSByb3dTdW1zKGR0bSkKCmNvcihsc2QuZGZbLGMoJ3BvcycsJ25lZycsJ3RvdF9hZmZlY3QnLCd0b3RfdG9rZW5zJyldKQpgYGAKCldlIHRlbmQgdG8gdGhpbmsgb2YgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIGFmZmVjdCBhcyBvbiB0aGUgc2FtZSBzY2FsZSwgc28gcGVyaGFwcyB3ZSBjYW4ganVzdCB1c2UgdGhlIGFic29sdXRlIGRpZmZlcmVuY2UgYXMgYSBtZWFzdXJlIG9mIHNlbnRpbWVudD8KCmBgYHtyfQpzZW50X2Fic2RpZmYgPC0gbHNkLmRmJHBvcyAtIGxzZC5kZiRuZWcKbmFtZXMoc2VudF9hYnNkaWZmKSA8LSBkb2NuYW1lcyhjb3JwKQoKc29ydChzZW50X2Fic2RpZmYsIGRlYz1UKVsxOjEwXSAjIE1vc3QgcG9zaXRpdmU/CnNvcnQoc2VudF9hYnNkaWZmLCBkZWM9RilbMToxMF0gIyBNb3N0IG5lZ2F0aXZlPwoKYGBgCgpIYXJyaXNvbiBhZ2Fpbi4gQXQgbGVhc3QgaGUncyBub3QgdGhlIG1vc3QgcG9zaXRpdmUgKmFuZCogdGhlIG1vc3QgbmVnYXRpdmUuIEJ1dCB0aGUgc2hvcnRlc3Qgc3BlZWNoLCBXYXNoaW5ndG9uJ3Mgc2Vjb25kIGluYXVndXJhbCBhdCBqdXN0IDEzNSB0b2tlbnMsIGlzIHRoZSBzZWNvbmQgbW9zdCBuZWdhdGl2ZT8gQW5kIHRoZSBtb3N0IG5lZ2F0aXZlIGlzIExpbmNvbG4ncyBzZWNvbmQgaW5hdWd1cmFsLCB0aGUgdGhpcmQgc2hvcnRlc3Q/CgpMZW5ndGggaXMgaGF2aW5nIGEgY291cGxlIG9mIGVmZmVjdHMgaGVyZS4gVGhlIG1vc3Qgb2J2aW91cyBpcyB0aGF0IHRoZSBiYXNlIHJhdGVzIGZvciBwb3NpdGl2ZSB0b2tlbnMgYW5kIG5lZ2F0aXZlIHRva2VucyBhcmUgZGlmZmVyZW50IC0tIGluYXVndXJhbHMgYXJlIG1vcmUgcG9zaXRpdmUgdGhhbiBuZWdhdGl2ZSwgd2hpY2ggbWFrZXMgc2Vuc2UgLS0gc28gdGhlIGxvbmdlciB0aGUgc3BlZWNoIGlzLCB0aGUgZ3JlYXRlciBpcyB0aGUgbGlrZWx5IGRpZmZlcmVuY2UgaW4gcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIGNvdW50czoKCmBgYHtyfQpwbG90KGxzZC5kZiR0b3RfdG9rZW5zLHNlbnRfYWJzZGlmZixwY2g9MTksIGNvbD1yZ2IoMCwwLDAsLjUpLCBsb2c9IngiLG1haW49IlNlbnRpbWVudCBNZWFzdXJlZCBieSBBYnNvbHV0ZSBEaWZmZXJlbmNlIGluIENvdW50cyIsIHhsYWIgPSAiVG90YWwgVG9rZW5zIiwgeWxhYiA9ICJTZW50aW1lbnQiKQpgYGAKClNvIHRoaXMgbGVhZHMgdXMgdG8gdGhlIGZpcnN0IGNvbnN0cnVjdGVkIG1lYXN1cmUgb2Ygc2VudGltZW50IGFjdHVhbGx5IHJlY29tbWVuZGVkIGJ5IFNvcm9rYSBhbmQgWW91bmcuIFRoZSBmcmFjdGlvbiAocGVyY2VudGFnZSB3b3VsZCBiZSB0aGUgc2FtZSB4MTAwKSBvZiBhZmZlY3QgdG9rZW5zIHRoYXQgYXJlIHBvc2l0aXZlIG1pbnVzIHRoZSBmcmFjdGlvbiBvZiBhZmZlY3QgdG9rZW5zIHRoYXQgYXJlIG5lZ2F0aXZlLiAgCgpgYGB7cn0KbHNkLmRmJHBvc2ZyYWMgPC0gbHNkLmRmJHBvcy9sc2QuZGYkdG90X2FmZmVjdApsc2QuZGYkbmVnZnJhYyA8LSBsc2QuZGYkbmVnL2xzZC5kZiR0b3RfYWZmZWN0CgpzZW50X2ZyYWNkaWZmIDwtIGxzZC5kZiRwb3NmcmFjIC0gbHNkLmRmJG5lZ2ZyYWMKbmFtZXMoc2VudF9mcmFjZGlmZikgPC0gZG9jbmFtZXMoY29ycCkKc29ydChzZW50X2ZyYWNkaWZmLCBkZWM9VClbMToxMF0Kc29ydChzZW50X2ZyYWNkaWZmLCBkZWM9RilbMToxMF0KCnN1bW1hcnkoc2VudF9mcmFjZGlmZikKCm1uLnNlbnRfZnJhY2RpZmYgPC0gbWVhbihzZW50X2ZyYWNkaWZmKQoKcGxvdChkb2N2YXJzKGNvcnApJFllYXIsc2VudF9mcmFjZGlmZiwgdHlwZT0ibCIsIG1haW49IlNlbnRpbWVudCBieSBGcmFjdGlvbiBEaWZmZXJlbmNlIiwgeGxhYj0iWWVhciIsIHlsYWIgPSAiU2VudGltZW50IiwgeWxpbT1jKC0xLDEpKQpsaW5lcyhjKDE3MDAsMzAwMCksYygwLDApLCBjb2w9ImdyYXkiKQpsaW5lcyhjKDE3MDAsMzAwMCksYyhtbi5zZW50X2ZyYWNkaWZmLG1uLnNlbnRfZnJhY2RpZmYpLCBjb2w9ImdyYXkiLCBsdHk9MikKYGBgCgooTm90ZSB0aGF0IHNpbmNlIGFmZmVjdCB0b2tlbnMgPSBwb3NpdGl2ZSB0b2tlbnMgKyBuZWdhdGl2ZSB0b2tlbnMsIHRoaXMgaXMgZnVuY3Rpb25hbGx5IGVxdWl2YWxlbnQgdG8ganVzdCB0aGUgZnJhY3Rpb24gb2YgYWZmZWN0IHRva2VucyB0aGF0IGFyZSBwb3NpdGl2ZS4gVGhlIGZvcm1lciBpcyBjZW50ZXJlZCBvbiAwIGFuZCBydW5zIGZyb20gLTEgdG8gMTsgdGhpcyBpcyBjZW50ZXJlZCBhdCAuNSBhbmQgcnVucyBmcm9tIDAgdG8gMS4gVGhleSBhcmUgY29ycmVsYXRlZCwgYnkgZGVmaW5pdGlvbiwgYXQgKzEpCgpgYGB7cn0Kc2VudF9mcmFjIDwtIGxzZC5kZiRwb3NmcmFjCm5hbWVzKHNlbnRfZnJhYykgPC0gZG9jbmFtZXMoY29ycCkKc29ydChzZW50X2ZyYWMsIGRlYz1UKVsxOjEwXQpzb3J0KHNlbnRfZnJhYywgZGVjPUYpWzE6MTBdCgpzdW1tYXJ5KHNlbnRfZnJhYykKCm1uLnNlbnRfZnJhYyA8LSBtZWFuKHNlbnRfZnJhYykKCnBsb3QoZG9jdmFycyhjb3JwKSRZZWFyLHNlbnRfZnJhYywgdHlwZT0ibCIsIG1haW49IlNlbnRpbWVudCBieSBGcmFjdGlvbiIsIHhsYWI9IlllYXIiLCB5bGFiID0gIlNlbnRpbWVudCIsIHlsaW09YygwLDEpKQpsaW5lcyhjKDE3MDAsMzAwMCksYygwLjUsMC41KSwgY29sPSJncmF5IikKbGluZXMoYygxNzAwLDMwMDApLGMobW4uc2VudF9mcmFjLG1uLnNlbnRfZnJhYyksIGNvbD0iZ3JheSIsIGx0eT0yKQoKYGBgCgpgYGB7cn0KcGxvdChsc2QuZGYkdG90X3Rva2VucyxzZW50X2ZyYWNkaWZmLHBjaD0xOSwgY29sPXJnYigwLDAsMCwuNSksIHlsaW0gPSBjKC0xLDEpLCBsb2c9IngiLG1haW49IlNlbnRpbWVudCBNZWFzdXJlZCBieSBGcmFjdGlvbiBEaWZmZXJlbmNlIiwgeGxhYiA9ICJUb3RhbCBUb2tlbnMiLCB5bGFiID0gIlNlbnRpbWVudCIpCmxpbmVzKGMoMSwxMDAwMCksYygwLDApLCBjb2w9ImdyYXkiKQpsaW5lcyhjKDEsMTAwMDApLGMobW4uc2VudF9mcmFjZGlmZixtbi5zZW50X2ZyYWNkaWZmKSwgY29sPSJncmF5IiwgbHR5PTIpCmBgYAoKT3Igd2UgY2FuIHN0YXJ0IGRvd24gdGhlIEZpZ2h0aW4gV29yZHMgbG9naWNhbCBwYXRoIGFuZCBsb29rIGF0IHRoZSBsb2dyYXRpbzoKCmBgYHtyfQpzZW50X2xvZ3JhdGlvIDwtIGxvZyhsc2QuZGYkcG9zKzEpIC0gbG9nKGxzZC5kZiRuZWcgKzEpCm5hbWVzKHNlbnRfbG9ncmF0aW8pIDwtIGRvY25hbWVzKGNvcnApCnNvcnQoc2VudF9sb2dyYXRpbywgZGVjPVQpWzE6MTBdCnNvcnQoc2VudF9sb2dyYXRpbywgZGVjPUYpWzE6MTBdCgpzdW1tYXJ5KHNlbnRfbG9ncmF0aW8pCgptbi5zZW50X2xvZ3JhdGlvIDwtIG1lYW4oc2VudF9sb2dyYXRpbykKCnBsb3QoZG9jdmFycyhjb3JwKSRZZWFyLHNlbnRfbG9ncmF0aW8sIHR5cGU9ImwiLCBtYWluPSJTZW50aW1lbnQgYnkgTG9ncmF0aW8iLCB4bGFiPSJZZWFyIiwgeWxhYiA9ICJTZW50aW1lbnQiLCB5bGltPWMoLTIsMikpCmxpbmVzKGMoMTcwMCwzMDAwKSxjKDAsMCksIGNvbD0iZ3JheSIpCmxpbmVzKGMoMTcwMCwzMDAwKSxjKG1uLnNlbnRfbG9ncmF0aW8sbW4uc2VudF9sb2dyYXRpbyksIGNvbD0iZ3JheSIsIGx0eT0yKQoKYGBgCgpUaGUgbG9ncmF0aW8gbWVhc3VyZSBpcyBjb3JyZWxhdGVkIHdpdGggdGhlIGZyYWN0aW9uIG1lYXN1cmUgYXQgLjk5OyB0aGVyZSBhcmUgbW9yZSBzdWJzdGFudGlhbCBkaWZmZXJlbmNlcyB3aGVuIHRoZSBmcmFjdGlvbnMgaW52b2x2ZWQgYXJlIG1vcmUgZXh0cmVtZS4KCkFuIGFkdmFudGFnZSBvZiB0aGUgbG9ncmF0aW8gaXMgdGhhdCB3ZSBjYW4gcmVtb3ZlIHRoZSBiYXNlIHJhdGVzIGlmIHRoYXQncyBhIGRlc2lyYWJsZSB0aGluZyB0byBkby4KYGBge3J9CnRvdF9wb3NfY291bnQgPC0gc3VtKGxzZC5kZiRwb3MrMSkKdG90X25lZ19jb3VudCA8LSBzdW0obHNkLmRmJG5lZysxKQpzZW50X3JlbGxvZ3JhdGlvIDwtIGxvZyhsc2QuZGYkcG9zKzEpIC0gbG9nKHRvdF9wb3NfY291bnQpIC0gKGxvZyhsc2QuZGYkbmVnICsxKSAtIGxvZyh0b3RfbmVnX2NvdW50KSkKbmFtZXMoc2VudF9yZWxsb2dyYXRpbykgPC0gZG9jbmFtZXMoY29ycCkKc29ydChzZW50X3JlbGxvZ3JhdGlvLCBkZWM9VClbMToxMF0Kc29ydChzZW50X3JlbGxvZ3JhdGlvLCBkZWM9RilbMToxMF0KCnN1bW1hcnkoc2VudF9yZWxsb2dyYXRpbykKCm1uLnNlbnRfcmVsbG9ncmF0aW8gPC0gbWVhbihzZW50X3JlbGxvZ3JhdGlvKQoKcGxvdChkb2N2YXJzKGNvcnApJFllYXIsc2VudF9yZWxsb2dyYXRpbywgdHlwZT0ibCIsIG1haW49IlNlbnRpbWVudCBieSBSZWxhdGl2ZSBMb2dyYXRpbyIsIHhsYWI9IlllYXIiLCB5bGFiID0gIlNlbnRpbWVudCIsIHlsaW09YygtMSwxKSkKbGluZXMoYygxNzAwLDMwMDApLGMoMCwwKSwgY29sPSJncmF5IikKbGluZXMoYygxNzAwLDMwMDApLGMobW4uc2VudF9yZWxsb2dyYXRpbyxtbi5zZW50X3JlbGxvZ3JhdGlvKSwgY29sPSJncmF5IiwgbHR5PTIpCgoKYGBgCgpBIGJpZ2dlciBhZHZhbnRhZ2UgaXMgdGhlICJGaWdodGluIFdvcmRzIiBsb2dpYyB0aGF0IGFsbG93cyB1cyB0byBjb3JyZWN0IGZvciB0aGUgaGV0ZXJvc2tlZGFzdGljaXR5IGFyaXNpbmcgZnJvbSBkb2N1bWVudCBsZW5ndGguIFRoZSBzdGFuZGFyZCBlcnJvciBmb3IgdGhlIGxvZygoYS9iKS8oYy9kKSkgd2hlbiBhLGIsYywgYW5kIGQgYXJlIFBvaXNzb24gZGlzdHJpYnV0ZWQgY291bnRzIGlzIH4gc3FydCgxL2EgKyAxL2IgKyAxL2MgKyAxL2QpLgoKYGBge3J9CnNlLnNlbnRfcmVsbG9ncmF0aW8gPSBzcXJ0KDEvKGxzZC5kZiRwb3MrMSkgKyAxL3RvdF9wb3NfY291bnQgKyAxLyhsc2QuZGYkbmVnKzEpICsgMS90b3RfbmVnX2NvdW50KQoKc2VudF96bG9ncmF0aW8gPSBzZW50X3JlbGxvZ3JhdGlvIC8gc2Uuc2VudF9yZWxsb2dyYXRpbwoKc3VtbWFyeShzZW50X3psb2dyYXRpbykKCnBsb3QobHNkLmRmJHRvdF90b2tlbnMsc2VudF96bG9ncmF0aW8scGNoPTE5LCBjb2w9cmdiKDAsMCwwLC41KSwgeWxpbSA9IGMoLTYsNiksIGxvZz0ieCIsbWFpbj0iU2VudGltZW50IE1lYXN1cmVkIGJ5IFJlbGF0aXZlIExvZ3JhdGlvIFotU2NvcmUiLCB4bGFiID0gIlRvdGFsIFRva2VucyIsIHlsYWIgPSAiU2VudGltZW50IikKbGluZXMoYygxLDEwMDAwKSxjKDAsMCksIGNvbD0iZ3JheSIpCiNsaW5lcyhjKDEsMTAwMDApLGMobW4uc2VudF9mcmFjZGlmZixtbi5zZW50X2ZyYWNkaWZmKSwgY29sPSJncmF5IiwgbHR5PTIpCiAgICAgICAgICAgICAgICAgICAgICAgICAKc29ydChzZW50X3psb2dyYXRpbyxkZWM9VClbMToxMF0Kc29ydChzZW50X3psb2dyYXRpbyxkZWM9RilbMToxMF0KYGBgCgpTbyAuLi4gZm91ciBvZiB0aGUgZml2ZSBtb3N0IG5lZ2F0aXZlIChvciBsZWFzdCBwb3NpdGl2ZSkgaW5hdWd1cmFsIGFkZHJlc3NlcyBhcmUgTGluY29sbidzIHR3bywgS2VubmVkeSdzLCBhbmQgT2JhbWEncyBmaXJzdD8gQWxsIG9mIHRob3NlIGFyZSBjb25zaWRlcmVkIGFtb25nIHRoZSBtb3N0IGluc3BpcmF0aW9uYWwsIHVwbGlmdGluZywgaG9wZWZ1bCBpbmF1Z3VyYWxzIGV2ZXIuIFdoYXQgZ2l2ZXM/CgpNYXliZSBpdCdzIExleGljb2Rlci4gT0ssIGxldCdzIGRyb3AgJDEwIGFuZCB0cnkgdGhlIExJV0MgZGljdGlvbmFyaWVzIC4uLgoKdGltZSBwYXNzZXMgLi4uIG1vbmV5IGZsb3dzCgoKYGBge3J9Cmxpd2MuZGYgPC0gcmVhZC5jc3YoIkxJV0MtSW5hdWd1cmFscy5jc3YiLCBoZWFkZXI9VFJVRSkKc2VudF9saXdjIDwtIGxpd2MuZGYkVG9uZQpuYW1lcyhzZW50X2xpd2MpIDwtIGRvY25hbWVzKGNvcnApCgpwbG90KGxzZC5kZiR0b3RfdG9rZW5zLHNlbnRfbGl3YyxwY2g9MTksIGNvbD1yZ2IoMCwwLDAsLjUpLCB5bGltID0gYygwLDEwMCksIGxvZz0ieCIsbWFpbj0iVG9uZSBNZWFzdXJlZCBieSBMSVdDIiwgeGxhYiA9ICJUb3RhbCBUb2tlbnMiLCB5bGFiID0gIlNlbnRpbWVudCIpCmxpbmVzKGMoMSwxMDAwMCksYyg1MCw1MCksIGNvbD0iZ3JheSIpCmBgYAoKU29tZXdoYXQgc2ltaWxhciB0byBzZW50X2ZyYWNkaWZmIGZyb20gTGV4aWNvZGVyLiBDb3JyZWxhdGVkIGF0IC42NywgaGlnaGVyIGluIHZhcmlhYmlsaXR5IGZvciBzaG9ydGVyIGRvY3VtZW50cywgYW5kIGluIGFncmVlbWVudCB0aGF0IExpbmNvbG4ncyBhbmQgS2VubmVkeSdzIGluYXVndXJhbHMgd2VyZSBhbW9uZyB0aGUgbW9zdCBuZWdhdGl2ZS4gTElXQyBzZWVtcyB0byBzYXR1cmF0ZSBvbiB0aGUgcG9zaXRpdmUgZW5kLCBtYWtpbmcgaXQgZGlmZmljdWx0IHRvIHNlZSByZWxhdGl2ZSBkaWZmZXJlbmNlcyBhbW9uZyB0aGUgbW9zdCBwb3NpdGl2ZS4KYGBge3J9CmNvcihzZW50X2xpd2MsIHNlbnRfZnJhY2RpZmYpICMgY29ycmVsYXRlZCAuNjcKCnNvcnQoc2VudF9saXdjLGRlYz1UKVsxOjEwXQpzb3J0KHNlbnRfbGl3YyxkZWM9RilbMToxMF0KYGBgCgpTbywgYWdhaW4sIHdoYXQgZ2l2ZXM/IFdoYXQgZ2l2ZXMgaXMgdGhhdCBzZW50aW1lbnQgYW5hbHlzaXMgYmFzZWQgb24gZGljdGlvbmFyaWVzLCBlc3BlY2lhbGx5IGRpY3Rpb25hcmllcyBidWlsdCBmb3IgZGlmZmVyZW50IGNvbnRleHRzIHRoYW4gdGhlIGFwcGxpY2F0aW9uLCBpcyBvZnRlbiBzbyBub2lzeSBhcyB0byBiZSBlZmZlY3RpdmVseSB1c2VsZXNzLgoKYGBge3J9CmxpbmNvbG5saW5lcyA8LSBjKCJXaXRoIG1hbGljZSB0b3dhcmQgbm9uZTsiLCAid2l0aCBjaGFyaXR5IGZvciBhbGw7IiwibGV0IHVzIHN0cml2ZSBvbiB0byBmaW5pc2ggdGhlIHdvcmsgd2UgYXJlIGluOyIsICJ0byBiaW5kIHVwIHRoZSBuYXRpb27igJlzIHdvdW5kczsiLCJ0byBjYXJlIGZvciBoaW0gd2hvIHNoYWxsIGhhdmUgYm9ybmUgdGhlIGJhdHRsZSwgYW5kIGZvciBoaXMgd2lkb3csIGFuZCBoaXMgb3JwaGFu4oCUIikKbGluY29sbmxpbmVzCmRmbShsaW5jb2xubGluZXMsCiAgICB0b2xvd2VyID0gVFJVRSwgICAgIyBjYXNlZm9sZAogICAgc3RlbSA9IEZBTFNFLCAgICAgICAgICAgICAgICAgIyBkbyBub3Qgc3RlbQogICAgcmVtb3ZlX3B1bmN0ID0gVFJVRSwgICAgICAgICAgIyByZW1vdmUgcHVuY3R1YXRpb24KICAgIGRpY3Rpb25hcnkgPSBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNSkKCgpgYGAKCkNvbnNpZGVyIHRoaXMgS2VubmVkeSBsaW5lOgoKYGBge3J9CmRmbSgiTGV0IHVzIG5ldmVyIG5lZ290aWF0ZSBvdXQgb2YgZmVhci4gQnV0IGxldCB1cyBuZXZlciBmZWFyIHRvIG5lZ290aWF0ZS4iLAogICAgdG9sb3dlciA9IFRSVUUsICAgICMgY2FzZWZvbGQKICAgIHN0ZW0gPSBGQUxTRSwgICAgICAgICAgICAgICAgICMgZG8gbm90IHN0ZW0KICAgIHJlbW92ZV9wdW5jdCA9IFRSVUUsICAgICAgICAgICMgcmVtb3ZlIHB1bmN0dWF0aW9uCiAgICBkaWN0aW9uYXJ5ID0gZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpCmBgYAoKT3IgdGhlc2U6CgpgYGB7cn0KamZrbGluZXMgPC0gYygiVGhlIGdyYXZlcyBvZiB5b3VuZyBBbWVyaWNhbnMgd2hvIGFuc3dlcmVkIHRoZSBjYWxsIHRvIHNlcnZpY2Ugc3Vycm91bmQgdGhlIGdsb2JlLiIsICJOb3cgdGhlIHRydW1wZXQgc3VtbW9ucyB1cyBhZ2FpbiIsICItLSBub3QgYXMgYSBjYWxsIHRvIGJlYXIgYXJtcywgdGhvdWdoIGFybXMgd2UgbmVlZDsiLCAibm90IGFzIGEgY2FsbCB0byBiYXR0bGUsIHRob3VnaCBlbWJhdHRsZWQgd2UgYXJlIiwiIC0tIGJ1dCBhIGNhbGwgdG8gYmVhciB0aGUgYnVyZGVuIG9mIGEgbG9uZyB0d2lsaWdodCBzdHJ1Z2dsZSwgeWVhciBpbiBhbmQgeWVhciBvdXQsIiwgJ1wicmVqb2ljaW5nIGluIGhvcGUsIHBhdGllbnQgaW4gdHJpYnVsYXRpb25cIicsICItLSBhIHN0cnVnZ2xlIGFnYWluc3QgdGhlIGNvbW1vbiBlbmVtaWVzIG9mIG1hbjogdHlyYW5ueSwgcG92ZXJ0eSwgZGlzZWFzZSwgYW5kIHdhciBpdHNlbGYuIiwiQ2FuIHdlIGZvcmdlIGFnYWluc3QgdGhlc2UgZW5lbWllcyBhIGdyYW5kIGFuZCBnbG9iYWwgYWxsaWFuY2UsIE5vcnRoIGFuZCBTb3V0aCwgRWFzdCBhbmQgV2VzdCwgdGhhdCBjYW4gYXNzdXJlIGEgbW9yZSBmcnVpdGZ1bCBsaWZlIGZvciBhbGwgbWFua2luZD8iKQpqZmtsaW5lcwpkZm0oamZrbGluZXMsCiAgICB0b2xvd2VyID0gVFJVRSwgICAgIyBjYXNlZm9sZAogICAgc3RlbSA9IEZBTFNFLCAgICAgICAgICAgICAgICAgIyBkbyBub3Qgc3RlbQogICAgcmVtb3ZlX3B1bmN0ID0gVFJVRSwgICAgICAgICAgIyByZW1vdmUgcHVuY3R1YXRpb24KICAgIGRpY3Rpb25hcnkgPSBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNSkKYGBgCgpJdCdzIHZlcnkgZGlmZmljdWx0IHRvIGJ1aWxkIGEgZGljdGlvbmFyeSB0aGF0IGNhcHR1cmVzIG1vcmUgc2lnbmFsIHRoYW4gbm9pc2UsIGVzcGVjaWFsbHkgYWNyb3NzIGRpZmZlcmVudCBzb3J0cyBvZiBjb250ZXh0cy4KCgo=