It can be useful to be able to classify new “test” documents using already classified “training” documents. A common example is using a corpus of labeled spam and ham (non-spam) e-mails to predict whether or not a new document is spam.
For this project, you can start with a spam/ham dataset, then predict the class of new documents (either withheld from the training dataset or from another source such as your own spam folder).
I downloaded and used a sms spam dataset collected by the UC Irvine Machine Learning Repository: https://archive.ics.uci.edu/dataset/228/sms+spam+collection
First we load our libraries, and then load our data through read.table(), setting sep “ to note that records are separated by tabs. We then rename and select our relevant columns for use.
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.4.4 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── 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(tm)
## Warning: package 'tm' was built under R version 4.3.3
## Loading required package: NLP
##
## Attaching package: 'NLP'
##
## The following object is masked from 'package:ggplot2':
##
## annotate
library(SnowballC)
library(e1071)
## Warning: package 'e1071' was built under R version 4.3.3
#load data
data <- read.table("SMSSpamCollection", sep = "\t", stringsAsFactors = FALSE)
## Warning in scan(file = file, what = what, sep = sep, quote = quote, dec = dec,
## : EOF within quoted string
#renaming and selecting relevant columns
a <- data %>%
rename(txt = V2,
flag = V1) %>%
select(txt, flag)
Using tm::Corpus, we convert our data into a corpus.
#converting data to corpus
text_corpus <- Corpus(VectorSource(a$txt), readerControl = list(language = "en", encoding = "UTF-8"))
text_corpus
## <<SimpleCorpus>>
## Metadata: corpus specific: 1, document level (indexed): 0
## Content: documents: 1779
tm_map allows us to perform various standard text cleaning operations, such as removing stopwords, whitespaces, and stemming, which is reduces a word to its root. ie: “running” would be reduced to “run”, for easier, more consistent text parsing.
#removing whitespace, converting text to lowercase, removing stopwords,
#removing punctionation, numbers, and stemming words within corpus
b <- text_corpus %>%
tm_map(stripWhitespace) %>%
tm_map(content_transformer(tolower)) %>%
tm_map(removeWords, stopwords("english")) %>%
tm_map(removePunctuation) %>%
tm_map(removeNumbers) %>%
tm_map(stemDocument)
## Warning in tm_map.SimpleCorpus(., stripWhitespace): transformation drops
## documents
## Warning in tm_map.SimpleCorpus(., content_transformer(tolower)): transformation
## drops documents
## Warning in tm_map.SimpleCorpus(., removeWords, stopwords("english")):
## transformation drops documents
## Warning in tm_map.SimpleCorpus(., removePunctuation): transformation drops
## documents
## Warning in tm_map.SimpleCorpus(., removeNumbers): transformation drops
## documents
## Warning in tm_map.SimpleCorpus(., stemDocument): transformation drops documents
Using tm to create a Document Term Matrix (dtm)
#create document term matrix using tm
dtm <- DocumentTermMatrix(b)
dtm
## <<DocumentTermMatrix (documents: 1779, terms: 6610)>>
## Non-/sparse entries: 33663/11725527
## Sparsity : 100%
## Maximal term length: 40
## Weighting : term frequency (tf)
We then set our sample size to 75% of the records to train our model on, and identify those records.
#set sample size, set indices
samp <- round(0.75 * nrow(dtm))
indices_train <- sample(seq_len(nrow(dtm)), size = samp)
#split data based on indices
train_data <- dtm[indices_train, ]
test_data <- dtm[-indices_train, ]
#extract labels based on indices
train_label <- a[indices_train, ]$flag
test_label <- a[-indices_train, ]$flag
We then use the identified training data to train our model
#train model
nb <- naiveBayes(as.matrix(train_data), as.factor(train_label))
Using our trained model, we predict the the results of the remaining 25% of records
#predict new test data labels based on trained model
result <- predict(nb, newdata = as.matrix(test_data))
#create and print confusion matrix based on test results
cm <- table(Predicted = result, Actual = test_label)
print(cm)
## Actual
## Predicted ham spam
## ham 0 0
## spam 380 65