Email Classification: Ham or Spam — A Practical Walkthrough

- Published on

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_rangeand 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 yourReference
Kaggle notebook: Emails Classification — Ham or Spam