<aside> <img src="/icons/hexagon-two-sixths_blue.svg" alt="/icons/hexagon-two-sixths_blue.svg" width="40px" /> Dataset Used: https://www.kaggle.com/datasets/ashfakyeafi/spam-email-classification/data
</aside>
Subject: the stock trading gunslinger fanny is merrill but muzo not colza attainder and penultimate like esmark perspicuous ramble is segovia not group try slung kansas tanzania yes chameleon or continuant clothesman no libretto is chesapeake but tight not waterway herald and hawthorn like chisel morristown superior is deoxyribonucleic not clockwork try hall incredible mcdougall yes hepburn or einsteinian earmark no sapling is boar but duane not plain palfrey and inflexible like huzzah pepperoni bedtime is nameable not attire try edt chronography optima yes pirogue or diffusion albeit no
<aside> <img src="/icons/book-closed_blue.svg" alt="/icons/book-closed_blue.svg" width="40px" /> Colab Notebook: https://colab.research.google.com/drive/1PhEkeTjNyowWWZ0uwM3FYdcjdV6An4Kl?usp=sharing
</aside>
For the data analysis part, I analyzed the total number of words for each email and then plot it.
def word_length(sentence):
return len(re.findall(r'\w+', sentence))
df['word_number'] = df['text'].apply(word_length)

Some of the things i noticed that there are some emails that have 7k words in total. So in order to remove the training time I opted to remove the emails with the total number of words exceeding 1.5k. This also resulted in higher accuracy for the model later on.
#Filtering the dataset
while(max(df['word_number']) >= 1500):
idx = df['word_number'].idxmax()
df.drop(idx, inplace= True)
plt.figure(figsize = (12, 8))
groups = df.groupby('spam')
label_map = {0: 'Not Spam', 1: 'Spam'}
for i, (name, group) in enumerate(groups):
sb.histplot(group['word_number'], bins=50, alpha=0.5, label=str(label_map[name]))
plt.title('Word Count Distribution by Class')
plt.xlabel('Word Count')
plt.ylabel('Frequency')
plt.legend()
plt.show()

I divided the distribution among the 2 classes to analyze further and see if there are any patterns on the total number of words.
As I’ve mentioned earlier, the dataset isn’t balanced which could result in a biased model. So in order to prevent this, I downsampled the dataset to make it 1:1 ratio for both classes.
df_not_spam = df[df['spam'] == 0]
df_spam = df[df['spam'] == 1]
df_not_spam_downsampled = df_not_spam.sample(n=len(df_spam), random_state=1)
df_balanced = pd.concat([df_not_spam_downsampled, df_spam])
df_balanced = df_balanced.sample(frac=1).reset_index(drop=True)
<aside> <img src="/icons/filtered_blue.svg" alt="/icons/filtered_blue.svg" width="40px" /> Preprocessing
</aside>
Onto the preprocessing step, for NLP/text-related models we need to a few things to make it optimal. This includes: