Back to Projects
ML Project

Titanic Survival Prediction

Download public Titanic CSV files, clean missing values, encode categorical features, train a classifier, and produce a submission-style CSV.

Final deliverable: A full Titanic classification notebook with validation metrics and submission.csv.

Dataset: Titanic public CSV mirror

The notebook downloads public Titanic CSV mirrors directly. The target is Survived in train.csv.

  • Survived: target label, 1 means survived
  • Pclass, Sex, Age, SibSp, Parch, Fare, Embarked: modeling features
  • Name, Ticket, Cabin: high-cardinality/text fields learners may engineer later
  • PassengerId: required for submission
Step 1

Download Titanic Data

Download public train.csv and test.csv files.

Your task

Run the public download cell and confirm train.csv and test.csv exist.

Try writing this cell
# Run this cell in Colab, Kaggle, or a local notebook.
# It downloads public dataset files directly, so you do not need Kaggle credentials.

from pathlib import Path
from urllib.request import urlretrieve
import zipfile

DATA_DIR = Path("data/titanic")
DATA_DIR.mkdir(parents=True, exist_ok=True)

def find_file(name):
    possible_roots = [DATA_DIR, Path("/kaggle/input")]
    for root in possible_roots:
        if root.exists():
            matches = list(root.rglob(name))
            if matches:
                return matches[0]
    raise FileNotFoundError(f"Could not find {name}. Run the dataset download cell first.")

files = {
    "train.csv": "https://raw.githubusercontent.com/agconti/kaggle-titanic/master/data/train.csv",
    "test.csv": "https://raw.githubusercontent.com/agconti/kaggle-titanic/master/data/test.csv"
}

for filename, url in files.items():
    target = DATA_DIR / filename
    if target.exists() and target.stat().st_size > 0:
        print(f"already exists: {target}")
        continue
    print(f"downloading {filename} ...")
    urlretrieve(url, target)
    print(f"saved: {target}")

for zip_path in DATA_DIR.glob("*.zip"):
    extract_dir = DATA_DIR / zip_path.stem
    extract_dir.mkdir(exist_ok=True)
    if any(extract_dir.iterdir()):
        print(f"already extracted: {extract_dir}")
        continue
    with zipfile.ZipFile(zip_path, "r") as zf:
        zf.extractall(extract_dir)
    print(f"extracted: {zip_path} -> {extract_dir}")

print("Files:")
all_files = [path for path in sorted(DATA_DIR.rglob("*")) if path.is_file()]
for path in all_files[:40]:
    print(path)
if len(all_files) > 40:
    print(f"... {len(all_files) - 40} more files not shown")
Expected output
A file list containing train.csv and test.csv.
Show reference solution
# Run this cell in Colab, Kaggle, or a local notebook.
# It downloads public dataset files directly, so you do not need Kaggle credentials.

from pathlib import Path
from urllib.request import urlretrieve
import zipfile

DATA_DIR = Path("data/titanic")
DATA_DIR.mkdir(parents=True, exist_ok=True)

def find_file(name):
    possible_roots = [DATA_DIR, Path("/kaggle/input")]
    for root in possible_roots:
        if root.exists():
            matches = list(root.rglob(name))
            if matches:
                return matches[0]
    raise FileNotFoundError(f"Could not find {name}. Run the dataset download cell first.")

files = {
    "train.csv": "https://raw.githubusercontent.com/agconti/kaggle-titanic/master/data/train.csv",
    "test.csv": "https://raw.githubusercontent.com/agconti/kaggle-titanic/master/data/test.csv"
}

for filename, url in files.items():
    target = DATA_DIR / filename
    if target.exists() and target.stat().st_size > 0:
        print(f"already exists: {target}")
        continue
    print(f"downloading {filename} ...")
    urlretrieve(url, target)
    print(f"saved: {target}")

for zip_path in DATA_DIR.glob("*.zip"):
    extract_dir = DATA_DIR / zip_path.stem
    extract_dir.mkdir(exist_ok=True)
    if any(extract_dir.iterdir()):
        print(f"already extracted: {extract_dir}")
        continue
    with zipfile.ZipFile(zip_path, "r") as zf:
        zf.extractall(extract_dir)
    print(f"extracted: {zip_path} -> {extract_dir}")

print("Files:")
all_files = [path for path in sorted(DATA_DIR.rglob("*")) if path.is_file()]
for path in all_files[:40]:
    print(path)
if len(all_files) > 40:
    print(f"... {len(all_files) - 40} more files not shown")
Why this step matters

A project notebook should own its data setup so learners can reproduce the workflow.

Common mistake
Loading only train.csv and forgetting test.csv is needed for the final prediction file.
Step 2

Load and Inspect Titanic Data

Read train.csv and inspect missing values and target balance.

Your task

Load train.csv, print shape, missing counts, and survival rate.

Try writing this cell
from pathlib import Path
import pandas as pd

possible_roots = [
    Path("data/titanic"),
    Path("/kaggle/input"),
]

def find_file(name):
    for root in possible_roots:
        if root.exists():
            matches = list(root.rglob(name))
            if matches:
                return matches[0]
    raise FileNotFoundError(f"Could not find {name}. Run the dataset download/load cell first.")

train_path = find_file("train.csv")
df = pd.read_csv(train_path)

print(train_path)
print(df.shape)
df.head()

# Inspect missing values

# Inspect survival rate
Expected output
Missing counts showing Age/Cabin/Embarked gaps and survival rate around 0.38.
Show reference solution
from pathlib import Path
import pandas as pd

possible_roots = [
    Path("data/titanic"),
    Path("/kaggle/input"),
]

def find_file(name):
    for root in possible_roots:
        if root.exists():
            matches = list(root.rglob(name))
            if matches:
                return matches[0]
    raise FileNotFoundError(f"Could not find {name}. Run the dataset download/load cell first.")

train_path = find_file("train.csv")
df = pd.read_csv(train_path)

print(train_path)
print(df.shape)
df.head()

display(df.isna().sum().sort_values(ascending=False))
print("survival rate:", round(df["Survived"].mean(), 3))
Why this step matters

Titanic is a cleaning project as much as a modeling project. Missing Age and Cabin values influence feature choices.

Common mistake
Dropping every row with a missing value and accidentally throwing away too much data.
Step 3

Choose Features and Preprocessing

Build a clean feature matrix with numeric and categorical preprocessing.

Your task

Choose simple starting features and create a ColumnTransformer for imputation and one-hot encoding.

Try writing this cell
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder

# Define feature columns

# Build preprocessing transformer
Expected output
A preprocessing object that can handle missing numeric/categorical values.
Show reference solution
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder

numeric_features = ["Pclass", "Age", "SibSp", "Parch", "Fare"]
categorical_features = ["Sex", "Embarked"]
features = numeric_features + categorical_features

numeric_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
])

categorical_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="most_frequent")),
    ("onehot", OneHotEncoder(handle_unknown="ignore")),
])

preprocess = ColumnTransformer([
    ("num", numeric_pipeline, numeric_features),
    ("cat", categorical_pipeline, categorical_features),
])
Why this step matters

Pipelines keep train/test preprocessing consistent. This prevents common leakage and submission-time column mismatch bugs.

Common mistake
Calling get_dummies separately on train and test, creating different columns.
Step 4

Train and Validate a Classifier

Fit a baseline model and evaluate accuracy on a validation split.

Your task

Train LogisticRegression inside a Pipeline and calculate validation accuracy.

Try writing this cell
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
from sklearn.model_selection import train_test_split

# Split train/validation

# Build model pipeline

# Fit and evaluate
Expected output
Validation accuracy and class-level precision/recall/F1.
Show reference solution
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
from sklearn.model_selection import train_test_split

X = df[features]
y = df["Survived"]

X_train, X_val, y_train, y_val = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

model = Pipeline([
    ("preprocess", preprocess),
    ("clf", LogisticRegression(max_iter=1000)),
])

model.fit(X_train, y_train)
val_preds = model.predict(X_val)

print("accuracy:", accuracy_score(y_val, val_preds))
print(classification_report(y_val, val_preds))
Why this step matters

A validation split gives feedback before submitting to Kaggle. The class report shows whether the model ignores one class.

Common mistake
Training on all data first and having no local validation signal.
Step 5

Add Simple Feature Engineering

Create family-size and alone features that are common in Titanic notebooks.

Your task

Add FamilySize and IsAlone, then retrain with those features.

Try writing this cell
# Create FamilySize and IsAlone in train data

# Update feature lists

# Retrain model
Expected output
A retrained model with engineered features.
Show reference solution
df["FamilySize"] = df["SibSp"] + df["Parch"] + 1
df["IsAlone"] = (df["FamilySize"] == 1).astype(int)

numeric_features = ["Pclass", "Age", "SibSp", "Parch", "Fare", "FamilySize", "IsAlone"]
categorical_features = ["Sex", "Embarked"]
features = numeric_features + categorical_features

preprocess = ColumnTransformer([
    ("num", numeric_pipeline, numeric_features),
    ("cat", categorical_pipeline, categorical_features),
])

X = df[features]
X_train, X_val, y_train, y_val = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

model = Pipeline([
    ("preprocess", preprocess),
    ("clf", LogisticRegression(max_iter=1000)),
])
model.fit(X_train, y_train)
val_preds = model.predict(X_val)
print("accuracy:", accuracy_score(y_val, val_preds))
Why this step matters

Feature engineering should be motivated by domain logic: family size may affect survival because evacuation happened in groups.

Common mistake
Creating features in train but forgetting to create the same features in test.
Step 6

Create Submission-Style CSV

Apply identical preprocessing to test.csv and save PassengerId/Survived predictions.

Your task

Load test.csv, create the same engineered features, predict, and save submission.csv.

Try writing this cell
# Load test.csv

# Create the same engineered features

# Predict and save submission.csv
Expected output
A submission.csv with PassengerId and Survived columns.
Show reference solution
test_df = pd.read_csv(find_file("test.csv"))
test_df["FamilySize"] = test_df["SibSp"] + test_df["Parch"] + 1
test_df["IsAlone"] = (test_df["FamilySize"] == 1).astype(int)

test_preds = model.predict(test_df[features])

submission = pd.DataFrame({
    "PassengerId": test_df["PassengerId"],
    "Survived": test_preds,
})
submission.to_csv("submission.csv", index=False)
display(submission.head())
Why this step matters

Submission code tests whether the whole workflow is reproducible on unseen rows.

Common mistake
Using features in train that do not exist in test.