AIMachine LearningData ScienceNotebooks

Email Classification: Ham or Spam — A Practical Walkthrough

By Alaa El-Maria
Picture of the author
Published on
email classification — ham vs spam

This post summarizes and styles concepts from the Kaggle notebook: Emails Classification — Ham or Spam.

Problem overview

Classify incoming emails into two buckets — ham (legitimate) or spam — using a traditional NLP pipeline. The workflow emphasizes clean preprocessing, robust vectorization, and simple, strong baselines.

End‑to‑end pipeline


1) Load and inspect

Start with a balanced dataset of labeled emails. Check class distribution, duplicates, and basic text stats (length, token counts).

import pandas as pd

df = pd.read_csv('emails.csv')  # columns: [text, label]
df['label'] = df['label'].map({'ham': 0, 'spam': 1})
print(df['label'].value_counts(normalize=True))

2) Clean and normalize text

Common steps: lowercasing, removing HTML, URLs, numbers, punctuation, stopwords; optional lemmatization.

import re
from bs4 import BeautifulSoup
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer

# nltk.download('stopwords'); nltk.download('wordnet')
stop_words = set(stopwords.words('english'))
lemmatizer = WordNetLemmatizer()

def clean_text(x):
  x = BeautifulSoup(x, 'lxml').get_text()
  x = x.lower()
  x = re.sub(r'https?://\S+|www\.\S+', ' ', x)
  x = re.sub(r'[^a-z\s]', ' ', x)
  tokens = [lemmatizer.lemmatize(t) for t in x.split() if t not in stop_words]
  return ' '.join(tokens)

df['text_clean'] = df['text'].astype(str).apply(clean_text)

3) Vectorize (BoW / TF‑IDF)

TF‑IDF is an effective baseline for email classification.

from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer

X_train, X_test, y_train, y_test = train_test_split(
  df['text_clean'], df['label'], test_size=0.2, random_state=42, stratify=df['label']
)

tfidf = TfidfVectorizer(ngram_range=(1,2), min_df=2, max_df=0.9)
Xtr = tfidf.fit_transform(X_train)
Xte = tfidf.transform(X_test)

4) Train simple, strong baselines

Linear models and Naive Bayes are fast, sturdy choices.

from sklearn.naive_bayes import MultinomialNB
from sklearn.linear_model import LogisticRegression

nb = MultinomialNB()
nb.fit(Xtr, y_train)

lr = LogisticRegression(max_iter=200)
lr.fit(Xtr, y_train)

5) Evaluate and compare

Use accuracy, precision/recall, F1, and confusion matrix. Prioritize recall if missing spam is costly; precision if false alarms are painful.

from sklearn.metrics import classification_report, confusion_matrix

def report(name, model):
  y_pred = model.predict(Xte)
  print(f'\n{name}')
  print(classification_report(y_test, y_pred, target_names=['ham','spam']))
  print(confusion_matrix(y_test, y_pred))

report('MultinomialNB', nb)
report('LogisticRegression', lr)

6) Production‑ready pipeline

Wrap all steps into a single Pipeline for easy serving and cross‑validation.

from sklearn.pipeline import Pipeline

pipe = Pipeline([
  ('tfidf', TfidfVectorizer(ngram_range=(1,2), min_df=2, max_df=0.9)),
  ('clf', LogisticRegression(max_iter=200))
])

pipe.fit(X_train, y_train)
print(pipe.score(X_test, y_test))

Hi there! Want to support my work?


Practical tips

  • Keep preprocessing minimal and reproducible. Over‑cleaning can remove signal.
  • N‑grams often improve spam detection. Tune ngram_range and regularization.
  • Start simple (NB/LogReg). Add complexity only if metrics justify it.
<Newsletter className="bg-omega-800 p-10" />

Stay Tuned

Want to become a AI Exper?
The best articles, links and news related to AI and Machine Learning delivered once a week to your

Reference

Kaggle notebook: Emails Classification — Ham or Spam

Stay Tuned

Want to become a AI Exper?
The best articles, links and news related to AI and Machine Learning delivered once a week to your