<jupyter_start><jupyter_text>SMS SPAM CLASSIFICATION==**Model 1**<jupyter_code>import re
import numpy as np
import pandas as pd
# split into training and testing sets
# USE from sklearn.model_selection import train_test_split to avoid seeing deprecation warning.
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from sklearn.naive_bayes import MultinomialNB
from porter_stemmer import PorterStemmer

df = pd.read_csv("data/sms-20190303110043.csv", quotechar='"', quoting=1)
cols = list(df.columns)

# Output printing out first 5 rows
df.head()<jupyter_output><empty_output><jupyter_text>Let us clean and stem our sms text<jupyter_code>STEM = PorterStemmer()

def repl_mpesa_ids(s:str, sub="xxmpesa confirmed") -> str:
    """Mpesa have unique IDs that needs replacing"""
    return re.sub(r"(^(\w)* confirm(\w)*)|(\w{32})", sub, s)

def repl_phone_nums(s:str, sub="xxphonenum") -> str:
    """Replace the hashed phone numbers"""
    return re.sub(r"^(\w{32})", sub, s)

def repl_num(s:str, sub="xxnum") -> str:
    """Removed digits"""
    if s.isdigit():
        return sub
    return s

def repl_measures(s:str, sub="xxmeasure") -> str:
    """measures such 1pm, 12am, 12kg, 12mb, 19th, 12:23 """
    return re.sub(r"((^(\d)+(\w)+$)|(\d+:\d+))", sub, s)

def repl_money(s:str, sub="xxcurr") -> str:
    """Remove money e.g Ksh12, Ksh3,227.00"""
    return re.sub(r"(((\w){2,4}(\d)+$)|(((\w){2,4})?(\d+)\,(\d+)\.(\d+))|(((\w){2,4})?(\d+)\.(\d+))|(\d+/\d+/\d+))", sub, s)

def word_stemming(word) -> iter:
    """Stemming using https://tartarus.org/martin/PorterStemmer/"""
    if not word:
        return word
    new_words = re.sub('[^0-9a-zA-Z]+', ' ', word.lower())
    return (STEM.stem(c, 0,len(c)-1) for c in new_words.split())

def preprocess(
        words: list,
        replace_unique=True,
        stem=True,
        replace_nums=True,
        replace_measures=True) -> iter:
    
    # Generalize money occurrence
    words = (repl_money(word) for word in words)
    
    # Generalize
    words = (repl_phone_nums(word) for word in words)
    
    # stemming
    if stem:
        new_words = ()
        for w in words:
            word_token = word_stemming(w)
            new_words += tuple(word_token)
        words = new_words
        del new_words
    
    # generalize all numbers
    if replace_nums:
        words = (repl_num(word) for word in words)
        
    # generalize all measures
    if replace_measures:
        words = (repl_measures(word) for word in words)
    
    return (word for word in words if word)

# Create a new column whose contents are in lower case
df['sms_text'] = df['body'].apply(lambda x: x.lower())

# Remove all mpesa references
df['sms_text'] = df['sms_text'].apply(repl_mpesa_ids)

# Clean and stem the column
df['sms_text'] = df['sms_text'].apply(lambda x: " ".join(preprocess(x.split())))

df.head()
words_array = count_vector.transform(df['sms_text'])
words_array
frequency_matrix = pd.DataFrame(words_array.toarray(), columns=count_vector.get_feature_names())
# TODO The matrix below has words that consist of numbers which we need to remove
frequency_matrix.iloc[2200 : 2250, :]
X_train, X_test, y_train, y_test = train_test_split(df['sms_text'], 
                                                    df['is_spam'], 
                                                    random_state=1)

print('Number of rows in the total set: {}'.format(df.shape[0]))
print('Number of rows in the training set: {}'.format(X_train.shape[0]))
print('Number of rows in the test set: {}'.format(X_test.shape[0]))<jupyter_output>Number of rows in the total set: 3656
Number of rows in the training set: 2742
Number of rows in the test set: 914<jupyter_text>Lets now train the data<jupyter_code>count_vector = CountVectorizer(stop_words="english")

# Fit the training data and then return the matrix
training_data = count_vector.fit_transform(X_train)

# Transform testing data and return the matrix. Note we are not fitting the testing data into the CountVectorizer()
testing_data = count_vector.transform(X_test)

naive_bayes = MultinomialNB()
naive_bayes.fit(training_data, y_train)

predictions = naive_bayes.predict(testing_data)<jupyter_output><empty_output><jupyter_text>**Precision:** Out of all SMSses that we predicted as SPAM, how many did we classify correctly?**Recall:** Out of SPAM SMSses, how many did we correctly classify as SPAM?In our model, we shouldn't care if `Recall score` is lower. However, we need `Precision score` to be higher.Why? It is okay for SPAM sms to be classified as not SPAM than the converse. An important SMS could be lost due to mis-classification.So this is a **high precision model**Let get the scores.<jupyter_code># Scores
print('Accuracy score: ', format(accuracy_score(y_test, predictions)))
print('Precision score: ', format(precision_score(y_test, predictions)))
print('Recall score: ', format(recall_score(y_test, predictions)))
print('F1 score: ', format(f1_score(y_test, predictions)))<jupyter_output>Accuracy score:  0.8916849015317286
Precision score:  0.8678414096916299
Recall score:  0.9099307159353349
F1 score:  0.8883878241262683
{'fraction_non_alphanumeric': 0.1296928327645051, 'fraction_numerical': 0.027872582480091012, 'mean_word_length': 4.067243035542748, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 22, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '205603', 'license': 'MIT', 'n_tokens_mistral': 1894, 'n_tokens_neox': 1751, 'n_words': 491, 'path': 'spam-classifier-mdl-1.ipynb', 'repo_name': 'kyrelos/ntw-2019', 'size': '5274'}