This milestone report forms an integral part of the Johns Hopkins Data Science Capstone Project. The primary objective of this project is to construct a predictive text application capable of suggesting the most probable next word given a sequence of preceding words.
This report covers the initial phase of the data science lifecycle:
1. Loading and inspecting the raw HC Corpora text
dataset (Blogs, News, and Twitter feeds). 2. Generating descriptive
summary statistics (file sizes, line counts, word counts). 3. Executing
clean text preprocessing and tokenization using quanteda
and tidytext. 4. Performing exploratory data analysis (EDA)
to evaluate Unigram, Bigram, and
Trigram frequency distributions. 5. Outlining the
architectural roadmap for building an N-gram language model with
Stupid Back-off smoothing and deploying it via an
interactive R Shiny application.
To maintain execution speed and memory safety during text
tokenization, we utilize high-performance R packages including
tidyverse, quanteda,
quanteda.textstats, and stringi.
# Core text mining and data manipulation libraries
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.5.3
## Warning: package 'ggplot2' was built under R version 4.5.3
## Warning: package 'dplyr' was built under R version 4.5.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.1.6
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.3 ✔ tibble 3.3.0
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.2.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tidytext)
## Warning: package 'tidytext' was built under R version 4.5.3
library(quanteda)
## Warning: package 'quanteda' was built under R version 4.5.3
## Package version: 4.5.0
## Unicode version: 15.1
## ICU version: 74.1
## Parallel computing: 2 of 2 threads used.
## See https://quanteda.io for tutorials and examples.
library(quanteda.textstats)
## Warning: package 'quanteda.textstats' was built under R version 4.5.3
library(stringi)
library(knitr)
The raw text files (en_US.blogs.txt,
en_US.news.txt, and en_US.twitter.txt) are
ingested using UTF-8 encoding.
# Define file path and URL
file_url <- "https://d396qusza40orc.cloudfront.net/dsscapstone/dataset/Coursera-SwiftKey.zip"
zip_file <- "Coursera-SwiftKey.zip"
# Download and unzip dataset if it doesn't exist
if (!file.exists("en_US.blogs.txt")) {
if (!file.exists(zip_file)) {
download.file(file_url, destfile = zip_file, method = "curl")
}
unzip(zip_file)
# The zip extracts files inside 'final/en_US/' subfolder — copy them to working directory
file.copy("final/en_US/en_US.blogs.txt", "en_US.blogs.txt")
file.copy("final/en_US/en_US.news.txt", "en_US.news.txt")
file.copy("final/en_US/en_US.twitter.txt", "en_US.twitter.txt")
}
# Read raw text files
blogs_raw <- readLines("en_US.blogs.txt", encoding = "UTF-8", skipNul = TRUE)
news_raw <- readLines("en_US.news.txt", encoding = "UTF-8", skipNul = TRUE)
twitter_raw <- readLines("en_US.twitter.txt", encoding = "UTF-8", skipNul = TRUE)
Before filtering or sampling, we assess the raw data size, record counts, total word counts, and average line lengths.
# Calculate file properties and metrics
get_file_size_mb <- function(file_path) {
if (file.exists(file_path)) {
return(round(file.info(file_path)$size / (1024^2), 2))
} else {
return(NA)
}
}
summary_df <- tibble(
Dataset = c("Blogs", "News", "Twitter"),
`File Size (MB)` = c(
get_file_size_mb("en_US.blogs.txt"),
get_file_size_mb("en_US.news.txt"),
get_file_size_mb("en_US.twitter.txt")
),
`Line Count` = c(length(blogs_raw), length(news_raw), length(twitter_raw)),
`Word Count` = c(
sum(stri_count_words(blogs_raw)),
sum(stri_count_words(news_raw)),
sum(stri_count_words(twitter_raw))
)
) %>%
mutate(`Mean Words per Line` = round(`Word Count` / `Line Count`, 2))
# Render clean markdown table
knitr::kable(summary_df, caption = "Table 1: Summary Statistics of HC Corpora Datasets")
| Dataset | File Size (MB) | Line Count | Word Count | Mean Words per Line |
|---|---|---|---|---|
| Blogs | 200.42 | 899288 | 37546806 | 41.75 |
| News | 196.28 | 1010206 | 34761151 | 34.41 |
| 159.36 | 2360148 | 30096690 | 12.75 |
Due to the substantial computational cost of processing full corpora in memory, we extract a 1% binomial random sample from each source to form a combined working corpus.
set.seed(42)
sample_rate <- 0.01
blogs_sample <- sample(blogs_raw, length(blogs_raw) * sample_rate)
news_sample <- sample(news_raw, length(news_raw) * sample_rate)
twitter_sample <- sample(twitter_raw, length(twitter_raw) * sample_rate)
combined_sample <- c(blogs_sample, news_sample, twitter_sample)
# Create Quanteda Corpus object
corp <- corpus(combined_sample)
# Tokenize with cleaning transformations
toks <- tokens(
corp,
remove_punct = TRUE,
remove_symbols = TRUE,
remove_numbers = TRUE,
remove_url = TRUE
) %>%
tokens_tolower()
We construct Document-Feature Matrices (DFM) to evaluate the top frequency distributions for Unigrams (single words), Bigrams (2-word pairs), and Trigrams (3-word sequences).
unigram_dfm <- tokens_ngrams(toks, n = 1) %>% dfm()
top_unigrams <- textstat_frequency(unigram_dfm, n = 15)
ggplot(top_unigrams, aes(x = reorder(feature, frequency), y = frequency)) +
geom_col(fill = "#2c3e50") +
coord_flip() +
labs(
title = "Top 15 Most Frequent Unigrams",
x = "Unigram Token",
y = "Frequency Count"
) +
theme_minimal()
bigram_dfm <- tokens_ngrams(toks, n = 2) %>% dfm()
top_bigrams <- textstat_frequency(bigram_dfm, n = 15)
ggplot(top_bigrams, aes(x = reorder(feature, frequency), y = frequency)) +
geom_col(fill = "#18bc9c") +
coord_flip() +
labs(
title = "Top 15 Most Frequent Bigrams",
x = "Bigram Sequence",
y = "Frequency Count"
) +
theme_minimal()
trigram_dfm <- tokens_ngrams(toks, n = 3) %>% dfm()
top_trigrams <- textstat_frequency(trigram_dfm, n = 15)
ggplot(top_trigrams, aes(x = reorder(feature, frequency), y = frequency)) +
geom_col(fill = "#e74c3c") +
coord_flip() +
labs(
title = "Top 15 Most Frequent Trigrams",
x = "Trigram Sequence",
y = "Frequency Count"
) +
theme_minimal()
With data exploration completed, the next phase focuses on building and optimizing the prediction model for real-time web deployment:
data.table structures..rds files.