This report explores three English-language text files commonly used for the SwiftKey text-prediction capstone: blogs, news, and Twitter messages. The purpose is to confirm that the data can be downloaded and read, describe the size and basic characteristics of each source, identify common words and differences between sources, and outline a practical approach for building a next-word prediction model and a Shiny application.
The analysis below is generated from the source files when this document is knitted. This keeps all reported counts and charts tied to the actual data rather than relying on estimates.
The analysis uses the public Coursera-SwiftKey data archive. The code downloads the archive if it is not already present, extracts it, and locates the three English-language files.
options(stringsAsFactors = FALSE)
data_dir <- file.path(path.expand("~"), "data")
dir.create(data_dir, showWarnings = FALSE)
zip_file <- file.path(data_dir, "Coursera-SwiftKey.zip")
download_url <- "https://d396qusza40orc.cloudfront.net/dsscapstone/dataset/Coursera-SwiftKey.zip"
if (!file.exists(zip_file)) {
download.file(download_url, destfile = zip_file, mode = "wb")
}
unzip_dir <- file.path(data_dir, "final")
if (!dir.exists(unzip_dir)) {
unzip(zip_file, exdir = data_dir)
}
# Locate the three English files, regardless of the exact extracted folder path.
all_txt <- list.files(data_dir, pattern = "\\.txt$", recursive = TRUE, full.names = TRUE)
find_file <- function(pattern) {
hits <- all_txt[grepl(pattern, basename(all_txt), ignore.case = TRUE)]
hits <- hits[grepl("en_US", hits, ignore.case = TRUE)]
if (length(hits) == 0) stop(paste("Could not find file matching:", pattern))
hits[1]
}
files <- c(
Blogs = find_file("blogs"),
News = find_file("news"),
Twitter = find_file("twitter")
)
# Read each file as lines. The original text is kept unchanged for the initial summaries.
text_data <- lapply(files, readLines, encoding = "UTF-8", warn = FALSE)
data.frame(
Source = names(files),
File = basename(files),
Loaded = file.exists(files),
stringsAsFactors = FALSE
)
## Source File Loaded
## 1 Blogs en_US.blogs.txt TRUE
## 2 News en_US.news.txt TRUE
## 3 Twitter en_US.twitter.txt TRUE
The table confirms which source files were located and loaded. The three sources represent different writing contexts: longer informal blog posts, edited news writing, and short social-media messages.
For each file, this section reports the number of lines, approximate word count, and file size. Word counts are calculated by splitting each non-empty line on whitespace; punctuation is removed only in the later word-frequency analysis.
count_words <- function(lines) {
nonempty <- lines[nzchar(trimws(lines))]
if (length(nonempty) == 0) return(0L)
sum(lengths(strsplit(trimws(nonempty), "\\\\s+")))
}
summary_table <- data.frame(
Source = names(text_data),
Lines = vapply(text_data, length, integer(1)),
Words_approx = vapply(text_data, count_words, integer(1)),
Size_MB = round(file.info(files)$size / 1024^2, 2),
row.names = NULL
)
knitr::kable(summary_table, caption = "Basic size summary of the three text files")
| Source | Lines | Words_approx | Size_MB |
|---|---|---|---|
| Blogs | 899288 | 899288 | 200.42 |
| News | 1010206 | 1010206 | 196.28 |
| 2360148 | 2360151 | 159.36 |
if (!requireNamespace("ggplot2", quietly = TRUE)) {
install.packages("ggplot2", repos = "https://cloud.r-project.org")
}
library(ggplot2)
ggplot(summary_table, aes(x = Source, y = Lines)) +
geom_col(fill = "steelblue") +
labs(title = "Number of text lines by source", x = NULL, y = "Lines") +
theme_minimal()
The line-count chart makes the relative scale of the sources easy to compare. The table should be used alongside the chart because line counts and word counts measure different things: a Twitter line is generally much shorter than a blog or news line.
The next chart examines the distribution of word lengths after basic token cleanup. Tokens are converted to lowercase and characters other than letters or apostrophes are removed. This is a lightweight exploratory cleaning step, not the final preprocessing pipeline.
clean_tokens <- function(lines) {
x <- tolower(paste(lines, collapse = " "))
tokens <- unlist(strsplit(x, "[^[:alpha:]']+"))
tokens <- tokens[nzchar(tokens)]
tokens
}
# Use a reproducible sample for detailed token analysis so the report remains
# practical on computers with limited memory. Full-file line and word counts
# are still reported in Section 2.
set.seed(2026)
sampled_text <- lapply(text_data, function(lines) {
lines <- lines[nzchar(trimws(lines))]
lines[sample.int(length(lines), min(length(lines), 100000))]
})
tokens_by_source <- lapply(sampled_text, clean_tokens)
token_summary <- data.frame(
Source = names(tokens_by_source),
Sample_Lines = vapply(sampled_text, length, integer(1)),
Tokens_in_Sample = vapply(tokens_by_source, length, integer(1)),
Unique_Tokens_in_Sample = vapply(tokens_by_source, function(x) length(unique(x)), integer(1)),
row.names = NULL
)
knitr::kable(token_summary, caption = "Token counts after basic cleanup")
| Source | Sample_Lines | Tokens_in_Sample | Unique_Tokens_in_Sample |
|---|---|---|---|
| Blogs | 1e+05 | 4176276 | 94600 |
| News | 1e+05 | 3374506 | 88040 |
| 1e+05 | 1265011 | 55192 |
word_lengths <- do.call(rbind, lapply(names(tokens_by_source), function(source) {
data.frame(Source = source, Word_Length = nchar(tokens_by_source[[source]]))
}))
ggplot(word_lengths, aes(x = Word_Length)) +
geom_histogram(binwidth = 1, boundary = 0, fill = "darkseagreen3", color = "white") +
facet_wrap(~ Source, ncol = 1) +
coord_cartesian(xlim = c(1, 20)) +
labs(title = "Distribution of word lengths", x = "Characters per token", y = "Token count") +
theme_minimal()
This plot helps identify the typical length of words in each source. Very long tokens can include unusual strings, URLs, or artifacts, so they should be reviewed before final model training.
The following tables show the 15 most frequent cleaned tokens in a reproducible sample of up to 100,000 non-empty lines per source. These counts are useful for understanding vocabulary and identifying common words. Common function words (such as “the”, “and”, and “to”) are expected to appear frequently; they are not automatically removed because they are important to next-word prediction.
top_n_words <- function(tokens, n = 15) {
tab <- sort(table(tokens), decreasing = TRUE)
head(data.frame(Word = names(tab), Count = as.integer(tab)), n)
}
for (source in names(tokens_by_source)) {
cat("\n\n###", source, "\n\n")
print(knitr::kable(top_n_words(tokens_by_source[[source]]),
caption = paste("Most frequent words:", source)))
}
##
##
## ### Blogs
##
##
##
## Table: Most frequent words: Blogs
##
## |Word | Count|
## |:----|------:|
## |the | 207502|
## |and | 121648|
## |to | 118523|
## |a | 100333|
## |of | 97601|
## |i | 92425|
## |in | 67254|
## |that | 52779|
## |it | 49391|
## |is | 48223|
## |for | 40721|
## |you | 34910|
## |with | 31778|
## |was | 31184|
## |on | 30620|
##
##
## ### News
##
##
##
## Table: Most frequent words: News
##
## |Word | Count|
## |:----|------:|
## |the | 195843|
## |to | 89929|
## |a | 88346|
## |and | 87926|
## |of | 77071|
## |in | 67057|
## |that | 34845|
## |for | 34820|
## |is | 28191|
## |on | 26878|
## |with | 25136|
## |said | 25039|
## |it | 23058|
## |he | 22809|
## |was | 22417|
##
##
## ### Twitter
##
##
##
## Table: Most frequent words: Twitter
##
## |Word | Count|
## |:----|-----:|
## |the | 40022|
## |to | 33686|
## |i | 31025|
## |a | 26122|
## |you | 23218|
## |and | 18700|
## |for | 16403|
## |in | 16036|
## |of | 15145|
## |is | 15127|
## |it | 12614|
## |my | 12520|
## |on | 11827|
## |that | 10024|
## |me | 8614|
top_plot_data <- do.call(rbind, lapply(names(tokens_by_source), function(source) {
top <- top_n_words(tokens_by_source[[source]], 10)
data.frame(Source = source, Word = top$Word, Count = top$Count)
}))
ggplot(top_plot_data, aes(x = reorder(Word, Count), y = Count)) +
geom_col(fill = "cornflowerblue") +
coord_flip() +
facet_wrap(~ Source, scales = "free", ncol = 1) +
labs(title = "Ten most frequent words in each source", x = NULL, y = "Frequency") +
theme_minimal()
The frequency tables and chart show which words dominate each source. Differences may reflect the writing context: news text often contains reporting language and named entities, blogs may contain more personal or conversational language, and Twitter may contain abbreviations, hashtags, mentions, and informal spellings.
Use the computed tables and plots above to confirm the following points before publishing:
Important: Replace this paragraph with two or three specific observations from your knitted output. For example, state which source has the most lines, which has the largest approximate word count, and one visible difference in the top-word lists. Do not submit unverified numerical claims.
The eventual application will suggest the next word based on the words a user has already typed. A practical development plan is:
The first version should favor a compact model that responds quickly over a very large model that is slow to load.
The Shiny app will provide a simple text box where a user enters a phrase. As the phrase changes, the app will display up to three suggested next words. The interface will also include a short explanation that predictions are based on patterns in the training text and may not always be grammatically or factually correct.
The app will be designed around three goals:
The exploratory analysis provides an initial understanding of the three text sources and establishes a reproducible starting point for the capstone. The summaries describe dataset size, while the plots and frequency tables highlight basic vocabulary and writing-style differences. The next milestone is to clean and sample the text, build n-gram frequency tables, evaluate prediction quality, and connect the selected model to a lightweight Shiny interface.
data folder.ggplot2 and knitr for reporting and
plots.