library(dplyr)
## Warning: package 'dplyr' was built under R version 4.5.2
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(tidytext)
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.5.2
library(ggridges)
library(stringr)
library(tidyr)
#Loading Files
# Path to your top-level folder
root <- "~/Desktop/Coursera/Data_sci_capstone/final"
# Find all .txt files in all subfolders
files <- list.files(
path = root,
#using regex to find .txt files start with en_US, '\\.' look for a literal period and 'txt$' looks fot txt at the end of the string
pattern = "^en_US.*\\.txt$$",
#recursive = TRUE is for looking in the subfolders of the root directory
recursive = TRUE,
#the final names of the files will be the full paths to them for readLines() later
full.names = TRUE,
#ignore the case of the naming
ignore.case = TRUE
)
#lapply() to loop over every element in the list
#function(f) takes each file as an argument
#run through readLines to get a character vector for each in the file
texts <- lapply(files, function(f) readLines(f, warn = FALSE))
#label each text with its file path
names(texts) <- files
#Exploratory Data Analysis
The goal of exploratory analysis will be to identify and visualize which individual words, pairs, and triplets (or ngrams) are most common in the data from each individual source. The most common ngrams’ positions within sentences will also be visualized. The same process will be followed with punctiuation: identify the most common ones and where they occur in sentences.
#sampling just 5% of each document so it runs faster
set.seed(123)
texts <- lapply(texts, function(x) {
n_keep <- ceiling(length(x) * 0.05)
x[sample(length(x), n_keep)]
})
sapply(texts, length)
## /Users/austenjack/Desktop/Coursera/Data_sci_capstone/final/en_US/en_US.blogs.txt
## 44965
## /Users/austenjack/Desktop/Coursera/Data_sci_capstone/final/en_US/en_US.news.txt
## 50513
## /Users/austenjack/Desktop/Coursera/Data_sci_capstone/final/en_US/en_US.twitter.txt
## 118008
This is an initialization of the data for exploratory analysis. For any given ngram, gather total frequency and sentence positions.
#making a table of all lines in all texts, adding a column that notes which source a line came from
lines_tbl <- bind_rows(lapply(texts, \(x) tibble(text = x)), .id = "source") %>%
mutate(source = basename(source))
#tokenizing the sentences in the lines_tbl table
sentences <- lines_tbl %>%
unnest_tokens(sentence, text, token = "sentences", to_lower = FALSE) %>%
mutate(sentence_id = row_number())
#making a function that will tokenize the words of sentences at different n-grams (single word, paris, triplets, etx)
ngram_tbl <- function(sentences, k, ngram_name) {
#take the sentences tokens df
sentences %>%
#tokenize ngrams based on argument k
unnest_tokens(ngram, sentence, token = "ngrams", n = k) %>%
#remove na's
filter(!is.na(ngram)) %>%
#group tokenized ngrams by which sentence they came from
group_by(sentence_id) %>%
#for each token add their position within their sentence
mutate(position = row_number(),
#also add the relative position within their sentence (.25, .5, etc)
rel_position = position / n()) %>%
#ungroup so the df is back to a normal df
ungroup()
}
#get dfs with individual words, pairs, and triplets including which source they came from and their positions within a sentence
words <- ngram_tbl(sentences, 1)
pairs <- ngram_tbl(sentences, 2)
trips <- ngram_tbl(sentences, 3)
#get frequencies come from the same table, add raw counts and counts per 100 ngrams
words_freq <- count(words, ngram, sort = TRUE) %>% mutate(per_100 = 100 * n / sum(n))
pairs_freq <- count(pairs, ngram, sort = TRUE) %>% mutate(per_100 = 100 * n / sum(n))
trips_freq <- count(trips, ngram, sort = TRUE) %>% mutate(per_100 = 100 * n / sum(n))
With the initialized data create summary stats of words for each source text. Total sentence count, word count, character count, average sentence length, total number of unique words, words per sentence, characters per word, etc.
#corpus summary table
#function for making a summary table for the text documents
corpus_summary <- function(lines_tbl, sentences, words) {
#with the table of lines (from before) make stats for each source
line_stats <- lines_tbl %>%
#group the lines table y source
group_by(source) %>%
#summarise total number of lines, total number of characters for each source
summarise(lines = n(),
characters = sum(str_length(text)),
.groups = "drop")
#get total number of sentences for each source
sentence_stats <- sentences %>%
count(source, name = "sentences")
#summarise word stats for each source
word_stats <- words %>%
group_by(source) %>%
#get total number words, total unique words
summarise(words = n(),
unique_words = n_distinct(ngram),
.groups = "drop")
#put all the stats together to make a summary table
line_stats %>%
left_join(sentence_stats, by = "source") %>%
left_join(word_stats, by = "source") %>%
#add other stats calculated by the ones collected: words_per_line, words_per_sentence, chars_per_word
mutate(words_per_line = round(words / lines, 1),
words_per_sentence = round(words / sentences, 1),
chars_per_word = round(characters / words, 1))
}
#get initial summary table using the function
summary_tbl <- corpus_summary(lines_tbl, sentences, words)
#want to add on a final row that has stats for all texts combined
#get total words in all texts
n_words <- nrow(words)
#get total unique words in all texts
n_unique <- n_distinct(words$ngram)
#manually make the final row of summary stats with the same column names as the full summary table
total_row <- tibble(
source = "All sources",
lines = nrow(lines_tbl),
characters = sum(str_length(lines_tbl$text)),
sentences = nrow(sentences),
words = n_words,
unique_words = n_unique
) %>%
mutate(words_per_line = round(words / lines, 1),
words_per_sentence = round(words / sentences, 1),
chars_per_word = round(characters / words, 1))
#bind the final row to the table
summary_tbl <- bind_rows(summary_tbl, total_row)
summary_tbl
Things to note: -twitter has fewer words per line and sentence than blog posts or news articles -all sources have extremely similar unique word count and characters per word
We have the summary statistics for each source, now will zoom in to get the frequencies of the 3 main ngrams being analyzed: individual words, pairs, and triplets.
#top_positions() will make a new table with only the most frequent words and their positions within sentences
top_positions <- function(pos, freq, n=10){
#use frequency table to get most frequent ngrams
n_top <- n
top_ngrams <- freq %>%
slice_max(n, n = n_top, with_ties = FALSE) %>%
#pull(ngram) returns the column as a vector, so after slicing it will pull the 'word' column with the top 10 words
pull(ngram)
#table with position values from before
pos %>%
#filter to only have the most frequent ngram
filter(ngram %in% top_ngrams) %>%
#group by ngrams to get stats on individual ngrams
group_by(ngram) %>%
#want total counts of ngram, average sentence position, median sentence position, average relative position
summarise(occurrences = n(),
mean_pos = mean(position),
median_pos = median(position),
mean_rel_pos = mean(rel_position)) %>%
#join the per_100 value calculated before
left_join(select(freq, ngram, per_100), by = "ngram") %>%
#arrange so most frequent words are first
arrange(desc(occurrences))
}
#get the top 10 ngrams of individual words, pairs, and triplets with their sentence positions
top_words <- top_positions(words, words_freq, 10)
top_pairs <- top_positions(pairs, pairs_freq, 10)
top_trips <- top_positions(trips, trips_freq, 10)
#plot each most common ngram's frequency using their counts per 100 ngrams
top_per_100_plot <- function(top_ngram, type){
ggplot(top_ngram, aes(reorder(ngram, -per_100), per_100)) +
geom_col() +
labs(x = type, y = paste("occurrences per 100 ", type, "s"))
}
Bar plots of each ngram dataset most common ngram frequency per 100 ngrams
top_words_plot <- top_per_100_plot(top_words, 'word')
top_words_plot
top_pairs_plot <- top_per_100_plot(top_pairs, 'pair')
top_pairs_plot
top_trips_plot <- top_per_100_plot(top_trips, 'triplet')
top_trips_plot
Notes: -“the” is the most common word used by far -“the” can also be
found in many of the most common pairs and triplets
#make similar plot but faceted by source
top_freq_by_source_plot <- function(tbl, type, n_top = 10) {
tbl %>%
count(source, ngram) %>%
group_by(source) %>%
mutate(per_100 = 100 * n / sum(n)) %>%
slice_max(n, n = n_top, with_ties = FALSE) %>%
ungroup() %>%
ggplot(aes(reorder_within(ngram, per_100, source), per_100)) +
geom_col() +
scale_x_reordered() +
facet_wrap(~ source, scales = "free_y") +
coord_flip() +
labs(x = type, y = paste0("occurrences per 100 ", type, "s"))
}
Bar plots of the most common ngrams across the three different sources
top_freq_by_source_plot(words, "word", 10)
top_freq_by_source_plot(pairs, "pair", 10)
top_freq_by_source_plot(trips, "triplet", 10)
Notes: -“the” is most common in all three sources -the sources generally
have the same top 10 most common words
Now visualizing distributions of the most common ngrams in sentences where they appear.
#function for making plots that show disributions of word locations in sentences
#arguments are title for the plot, the df with position data, df with frequency/counts data, and how many top ngrams to plot
top_sentence_dist_plot <- function(title, pos, freq, n = 10) {
#get top ngrams as have done before
n_top <- n
top_ngrams <- freq %>%
slice_max(.data$n, n = n_top, with_ties = FALSE) %>%
pull(ngram)
#with the position df to plot relative position of top ngrams in density plots
pos %>%
filter(ngram %in% top_ngrams) %>%
#rev() makes the most frequent ngram appears at the top of the plot
mutate(ngram = factor(ngram, levels = rev(top_ngrams))) %>%
ggplot(aes(x = rel_position, y = ngram, fill = ngram)) +
geom_density_ridges(
alpha = 0.8,
scale = 1.5,
rel_min_height = 0.01
) +
scale_fill_viridis_d() +
scale_x_continuous(limits = c(0, 1), expand = c(0, 0)) +
labs(title = title, x = "Relative position", y = NULL) +
theme_ridges() +
theme(legend.position = "none")
}
top_sentence_dist_plot("Sentence Distribution of Most Common Words",words, words_freq, 10)
## Picking joint bandwidth of 0.024
top_sentence_dist_plot("Sentence Distribution of Most Common Pairs", pairs, pairs_freq, 10)
## Picking joint bandwidth of 0.0386
top_sentence_dist_plot("Sentence Distribution of Most Common Triplets",trips, trips_freq, 10)
## Picking joint bandwidth of 0.0586
Notes: -individual words generally have uniform use throughout sentences
-the word “I” is more frequent at the beginning of a sentence -the most
common pairs have higher frequencies toward the end of sentences -most
common triplets have varying distributions
Doing the same thing but for each source.
#make same plots as before but facet by source
top_sentence_dist_plot_source <- function(pos, freq, n = 10) {
n_top <- n
top_ngrams <- freq %>%
slice_max(.data$n, n = n_top, with_ties = FALSE) %>%
pull(ngram)
pos %>%
filter(ngram %in% top_ngrams) %>%
mutate(ngram = factor(ngram, levels = rev(top_ngrams))) %>%
ggplot(aes(x = rel_position, y = ngram, fill = ngram)) +
geom_density_ridges(alpha = 0.8, scale = 1.5, rel_min_height = 0.01) +
scale_fill_viridis_d() +
scale_x_continuous(limits = c(0, 1), expand = c(0, 0)) +
facet_wrap(~ source) +
labs(x = "Relative position", y = NULL) +
theme_ridges() +
theme(legend.position = "none")
}
top_sentence_dist_plot_source(pos = words, freq = words_freq, 10)
## Picking joint bandwidth of 0.0295
## Picking joint bandwidth of 0.0309
## Picking joint bandwidth of 0.0295
top_sentence_dist_plot_source(pos = pairs, freq = pairs_freq, 10)
## Picking joint bandwidth of 0.048
## Picking joint bandwidth of 0.0477
## Picking joint bandwidth of 0.0476
top_sentence_dist_plot_source(pos = trips, freq = trips_freq, 10)
## Picking joint bandwidth of 0.0828
## Picking joint bandwidth of 0.0797
## Picking joint bandwidth of 0.0715
Notes: -individual ngrams -the distributions of single word ngrams in
sentences are consistent across sources, but twitter has slightly weird
distributions likely due to short sentences -pairs -most common pairs
also have similar distributions -triplets -most common triplets seem to
have slighly different distributions across the different sources
Getting the stats for punctuation as with single word ngrams
#punctuation
punct_tbl <- lines_tbl %>%
mutate(punct = str_extract_all(text, "[\\p{P}--['’]]")) %>%
select(source, punct) %>%
unnest(punct)
punct_freq <- count(punct_tbl, punct, sort = TRUE) %>%
mutate(per_100 = 100 * n / sum(n))
punct_freq
Punctuation frequency across different sources
ggplot(punct_freq, aes(reorder(punct, -n), n)) +
geom_col() +
labs(x = "punctuation mark", y = "count")
# by source, as a share of each source's punctuation
punct_tbl %>%
count(source, punct) %>%
group_by(source) %>%
mutate(per_100 = 100 * n / sum(n)) %>%
slice_max(n, n = 10, with_ties = FALSE) %>%
ungroup() %>%
ggplot(aes(reorder_within(punct, per_100, source), per_100)) +
geom_col() +
scale_x_reordered() +
facet_wrap(~ source, scales = "free_y") +
coord_flip() +
labs(x = "punctuation mark", y = "per 100 punctuation marks")
Notes: -Twitter generally has higher rates of punctuation use thatn
blogs and news articles -the overall distributions of the frequencies
follow a similar shape for all three sources
punct_pattern <- "[\\p{P}--['’]]"
punct_pos <- sentences %>%
mutate(len = str_length(sentence),
loc = lapply(str_locate_all(sentence, punct_pattern), as.data.frame)) %>%
select(source, sentence_id, sentence, len, loc) %>%
unnest(loc) %>%
mutate(ngram = str_sub(sentence, start, end),
position = start,
rel_position = start / len) %>%
select(source, sentence_id, ngram, position, rel_position)
punct_freq <- count(punct_pos, ngram, sort = TRUE) %>%
mutate(per_100 = 100 * n / sum(n))
Sentence distribution plots of most common punctuation and across different sources
#sentence distributions of punctuation
top_sentence_dist_plot("Sentence Distribution of Most Common Punctuation", punct_pos, punct_freq, 10)
## Picking joint bandwidth of 0.0248
top_sentence_dist_plot_source(pos = punct_pos, freq = punct_freq, 10)
## Picking joint bandwidth of 0.0375
## Picking joint bandwidth of 0.0379
## Picking joint bandwidth of 0.0291
Notes: -punctutaion marks show exaclty where we’d expect them to in
sentences -distributions in the three sources follow each other
closely