Exploring President Obama’s Executive Orders Using Text Analysis

Author

Grace Flandreau

Published

June 1, 2026

Packages

Uploading Files

Tidy text & word frequencies

The unnest_tokens function

Code
full_text_tidy <- eo_data |>
  select(document_number, title, publication_date, signing_date, full_text) |>
  unnest_tokens(word, full_text)

Removing non-alphanumeric characters

Code
full_text_tidy <- full_text_tidy %>%
  mutate(word = str_extract(word, "[a-z']+"))

Stop words

Code
full_text_tidy <- full_text_tidy %>%
  anti_join(stop_words)

full_text_tidy
# A tibble: 173,215 × 5
   document_number title                     publication_date signing_date word 
   <chr>           <chr>                     <date>           <date>       <chr>
 1 2016-30101      Relating to the Implemen… 2016-12-13       2016-12-08   title
 2 2016-30101      Relating to the Implemen… 2016-12-13       2016-12-08   <NA> 
 3 2016-30101      Relating to the Implemen… 2016-12-13       2016-12-08   pres…
 4 2016-30101      Relating to the Implemen… 2016-12-13       2016-12-08   exec…
 5 2016-30101      Relating to the Implemen… 2016-12-13       2016-12-08   <NA> 
 6 2016-30101      Relating to the Implemen… 2016-12-13       2016-12-08   dece…
 7 2016-30101      Relating to the Implemen… 2016-12-13       2016-12-08   <NA> 
 8 2016-30101      Relating to the Implemen… 2016-12-13       2016-12-08   rela…
 9 2016-30101      Relating to the Implemen… 2016-12-13       2016-12-08   impl…
10 2016-30101      Relating to the Implemen… 2016-12-13       2016-12-08   conv…
# ℹ 173,205 more rows
Code
meaningless_words <- tibble(word=c("thereof", "section", "sec", "ii", "iii", "iv", "vi", "vii", "viii"))
full_text_tidy <- full_text_tidy %>%
  anti_join(meaningless_words)

Analysing word frequencies

Code
fulltext_freq <- full_text_tidy %>%
  filter(!is.na(word)) %>%
  count(word, sort=T)

head(fulltext_freq, 20)
# A tibble: 20 × 2
   word            n
   <chr>       <int>
 1 united       2068
 2 executive    1994
 3 agencies     1523
 4 agency       1463
 5 secretary    1381
 6 federal      1379
 7 president    1298
 8 law          1214
 9 national     1187
10 department   1090
11 government   1058
12 including    1008
13 act           979
14 information   974
15 council       951
16 security      933
17 person        883
18 director      775
19 office        747
20 authority     726

Plotting word frequencies with bar graphs

Code
fulltext_freq %>%
  group_by(word) %>%
  summarise(n = sum(n)) %>%
  filter(n>800) %>%
  ggplot(aes(x=n, y=reorder(word, n), fill=n)) +
  geom_col(fill = "cadetblue")+
  labs(
    x= "Frequency",
    y= "Word",
    title = str_wrap("Most Frequent Words in President Obama's Executive Orders", width = 50)
  ) +
  theme_minimal()

TF-IDF

Code
fulltext_idf <- full_text_tidy %>%
  filter(word != "na") %>%
  count(title, word, sort = TRUE) %>%
  bind_tf_idf(word, title, n)

fulltext_idf %>%
  select(title, word, tf_idf) %>%
  arrange(desc(tf_idf))
# A tibble: 66,736 × 3
   title                                                            word  tf_idf
   <chr>                                                            <chr>  <dbl>
 1 Accelerating Improvements in HIV Prevention and Care in the Uni… hiv    0.338
 2 Interagency Group on Insular Areas                               igia   0.321
 3 Correction                                                       page   0.307
 4 Advancing the Global Health Security Agenda To Achieve a World … ghsa   0.296
 5 Amendment to Executive Order 11155, Awards for Special Capabili… scho…  0.264
 6 Chesapeake Bay Protection and Restoration                        ches…  0.258
 7 President's Council of Advisors on Science and Technology        pcast  0.257
 8 Establishment of the President's Economic Recovery Advisory Boa… perab  0.251
 9 Hostage Recovery Activities                                      host…  0.245
10 Amendments to Executive Order 12777                              liab…  0.241
# ℹ 66,726 more rows
Code
#| fig-width: 14
#| fig-height: 14

fulltext_idf %>%
  filter(title %in% c("Interagency Group on Insular Areas", "President's Council of Advisors on Science and Technology", "Hostage Recovery Activities", "Northern Bering Sea Climate Resilience", "Safeguarding the Nation From the Impacts of Invasive Species", "Implementing the National HIV/AIDS Strategy for the United States for 2015-2020")) %>%
  group_by(title) %>%
  arrange(desc(tf_idf)) %>%
  top_n(5, tf_idf) %>%
  ggplot(aes(x=tf_idf, y=reorder(word, tf_idf), fill = title)) +
  geom_col(show.legend = F) +
  labs(x="TF_IDF", y= "Word") +
  facet_wrap(~title, scales = "free", labeller = label_wrap_gen(width = 17)) +
  theme_minimal()

Analyzing n-grams

Code
eo_structured <- eo_data %>%
  select(document_number, title, signing_date, full_text) %>%
  filter(!is.na(full_text)) %>%
  mutate(
    sections = str_split(full_text, "(?=Sec\\.\\s*\\d|Section\\s+\\d)")
  ) %>%
  unnest(sections) %>%
  mutate(
    section_text = str_trim(sections),
    section_num = row_number()
  ) %>%
  filter(section_text != "")

Bigrams

Code
eo_bigrams <- eo_data %>%
  select(document_number, title, signing_date, full_text) %>%
  filter(!is.na(full_text)) %>%
  filter(year(signing_date) == 2015) %>%
  unnest_tokens(bigram, full_text, token = "ngrams", n = 2)

Counting n-grams

Code
eo_bigrams %>%
  count(bigram) %>%
  arrange(desc(n))
# A tibble: 15,910 × 2
   bigram              n
   <chr>           <int>
 1 of the            571
 2 this order        382
 3 of this           238
 4 to the            219
 5 united states     208
 6 the united        179
 7 executive order   135
 8 by the            118
 9 with the          116
10 shall be           99
# ℹ 15,900 more rows
Code
eo_bigrams %>%
  drop_na(bigram)
# A tibble: 37,665 × 4
   document_number title                                     signing_date bigram
   <chr>           <chr>                                     <date>       <chr> 
 1 2015-05677      Blocking Property and Suspending Entry o… 2015-03-08   title…
 2 2015-05677      Blocking Property and Suspending Entry o… 2015-03-08   3 the 
 3 2015-05677      Blocking Property and Suspending Entry o… 2015-03-08   the p…
 4 2015-05677      Blocking Property and Suspending Entry o… 2015-03-08   presi…
 5 2015-05677      Blocking Property and Suspending Entry o… 2015-03-08   execu…
 6 2015-05677      Blocking Property and Suspending Entry o… 2015-03-08   order…
 7 2015-05677      Blocking Property and Suspending Entry o… 2015-03-08   13692…
 8 2015-05677      Blocking Property and Suspending Entry o… 2015-03-08   of ma…
 9 2015-05677      Blocking Property and Suspending Entry o… 2015-03-08   march…
10 2015-05677      Blocking Property and Suspending Entry o… 2015-03-08   8 201…
# ℹ 37,655 more rows

Removing stopwords from n-grams

Code
eo_bigrams <- eo_bigrams %>%
  separate(col = bigram,
           into = c("word1", "word2"),
           sep = " ",
           remove = F)

eo_bigrams_stop <- eo_bigrams %>%
  filter(!word1 %in% stop_words$word & !word2 %in% stop_words$word) %>%
  filter(!str_detect(bigram, "^sec(tion)?\\s+\\d")) %>%
  filter(!str_detect(bigram, "^title\\s+\\d")) %>%
  filter(!str_detect(word1, "^\\d+$") & !str_detect(word2, "^\\d+$")) %>%
  filter(!str_detect(bigram, "^fr\\s+"))

eo_bigrams_stop %>%
  count(bigram, sort = T)
# A tibble: 4,283 × 2
   bigram                   n
   <chr>                <int>
 1 national security       55
 2 billing code            34
 3 federal government      32
 4 white house             32
 5 authority vested        29
 6 applicable law          28
 7 greenhouse gas          28
 8 departments agencies    26
 9 homeland security       26
10 steering committee      25
# ℹ 4,273 more rows

Plot bigram frequencies

Code
eo_bigrams_stop %>%
  count(bigram, sort = T) %>%
  filter(n>20) %>%
  ggplot(aes(x=reorder(bigram, n),
             y = n,
             fill = n)) +
  geom_col(fill = "sienna1") +
  labs(x = NULL, y = "frequency",
       title = str_wrap("Most Frequent Bigrams in President Obama's 2015 Executive Orders", width = 50)) +
  coord_flip() +
  theme_minimal()

Filtering n-grams

Code
eo_bigrams_stop %>%
  filter(str_detect(bigram, "government")) %>%
  distinct(bigram)
# A tibble: 57 × 1
   bigram                   
   <chr>                    
 1 antigovernment protests  
 2 antigovernment protestors
 3 term government          
 4 government consistent    
 5 government effort        
 6 government industry      
 7 federal government       
 8 government approach      
 9 government strategy      
10 representing government  
# ℹ 47 more rows

TF_IDF with n-grams

Code
eo_bigrams_tfidf <- eo_bigrams_stop %>%
  count(document_number, bigram) %>%
  bind_tf_idf(bigram, document_number, n)

eo_bigrams_tfidf %>%
  arrange(desc(tf_idf))
# A tibble: 5,294 × 6
   document_number bigram                    n     tf   idf tf_idf
   <chr>           <chr>                 <int>  <dbl> <dbl>  <dbl>
 1 2015-15828      presidential scholars     2 0.118   3.37  0.396
 2 2015-01255      controlled equipment     23 0.0888  3.37  0.299
 3 2015-29498      emergency board           7 0.109   2.67  0.292
 4 2015-31749      thursday december         3 0.0811  3.37  0.273
 5 2015-16334      advisory board           12 0.12    2.27  0.272
 6 2015-25744      security medal            7 0.0805  3.37  0.271
 7 2015-32582      attached hereto           6 0.08    3.37  0.269
 8 2015-15495      courts martial            3 0.0769  3.37  0.259
 9 2015-15495      martial united            3 0.0769  3.37  0.259
10 2015-18292      u.s trade                 9 0.0744  3.37  0.250
# ℹ 5,284 more rows

TF_IDF Plot

Code
set.seed(148)
selected_docs <- sample(unique(eo_bigrams_tfidf$document_number), 8)

eo_bigrams_tfidf %>%
  filter(document_number %in% selected_docs) %>%
  group_by(document_number) %>%
  slice_max(tf_idf, n = 4) %>%
  ungroup() %>%
  ggplot() +
  aes(x = tf_idf,
      y = fct_reorder(bigram, tf_idf),
      fill = document_number) +
  geom_col(show.legend = F) +
  facet_wrap(~document_number, scales = "free") +
  labs(x = "TF_IDF", y = NULL) +
  theme_minimal()

Creating and visualizing a bigram network

Code
eo2015_graph <- eo_bigrams_stop %>%
  count(word1, word2) %>%
  filter(n > 15) %>%
  graph_from_data_frame()

set.seed(1049)

a <- grid::arrow(type = "closed", length = unit(.13, "inches"))

ggraph(eo2015_graph, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n),
                 show.legend = F,
                 arrow = a, end_cap = circle(.01, "inches")) +
  geom_node_point(color = "blue3", size = 3) +
  geom_node_text(aes(label = name), vjust = 0.5, hjust = 0.5, repel = T) +
  theme_void() +
  labs(title = "Bigrams (two-word combinations) in Obama's 2015 Executive Orders")

Write Up


I chose to look at President Obama’s Executive Orders because last semester I was looking at AI Policy and browsed through a lot of the EOs released by Donald Trump, and I was curious to see the key words and ideas in Obama’s EOs. Executive Orders provide insight into the issues a president chooses to address through unilateral authority. This project analyzes the full set of executive orders issued by President Barack Obama during his two terms, from 2009 to 2017. The Federal Register API served as the primary data source because it provides structured and publicly accessible records of executive actions. While the word frequency and tf-idf analyses use the full corpus, the n-gram analysis focuses on executive orders issued in 2015. This narrower scope provides a snapshot of Obama’s second-term agenda during a period marked by activity in areas such as climate policy, national security, and government administration.

The word frequency analysis highlights the highly procedural nature of executive orders. Terms such as “federal,” “agency,” “secretary,” and “authority” appear frequently across the corpus, reflecting the fact that executive orders are primarily directives to government agencies rather than public-facing political statements. While useful, simple word counts reveal relatively little about the specific policy content of individual orders. Something I found particularly interesting in the analysis was that there was less of an overlap between the most common single words and the bigrams than I anticipated. I was not expecting the word section, because I filtered out all the section combinations, as they were acting as stop words as opposed to content, but I was expecting a greater overlap in some of the other words. For the bigrams, I was surprised that “United States” was not a combination that appeared over 20 times throughout the 2015 EOs, especially considering “united” was the second most used word in all of the EOs. I’m assuming that reference to the nation was done so in a broader way, but it was still interesting to note.

A limitation that I noticed was that when filtering for 2015 EOs, it was more difficult to sort words by their TF-IDF. Since there were so many fewer words compared to the entire dataset, there was less variety in the TF-IDF scores, which made graphing a bit more complicated. Because of this, I am inclined to prioritize larger sets of data because it gives more variety and more concrete analysis. Of course, large datasets have their issues, such as having to manually wade through some of the data, but I found in this assignment that working with the entire corpora was easier than just the 2015 data.

LLM Statement: When scraping the .txt files from the dataset, I was running into issues with Federal Government request access. To figure out how to remedy this, I copy and pasted the request access note and asked ClaudeAI how I could get around this problem. ClaudeAI suggested a for loop and the use of the federal registrar website, as opposed to the dataset itself. ClaudeAI also gave me the code used from lines 25 to 56 to get around the issue. I looked over the code and when I ran it, it worked as intended. I also used ClaudeAI for lines 60 to 64, as I could not figure out how to combine my .txt files into one document to use for tidytext analysis. For this, I told Claude AI my problem and asked for a few different ways to go about combining the files. I decided on the one I ended up using because it was the most straightforward and would be the easiest to use for the purposes of the assignment.