================================================================================
  Question 55: Titanic Classification with Logistic Regression (Comprehensive EDA & Prediction)
================================================================================

FULL QUESTION:
Implement a comprehensive machine learning pipeline on the Titanic dataset using Logistic Regression:
1. Data loading and initial inspection.
2. Exploratory Data Analysis (EDA) and visualizations of survival rates across age, gender, and passenger class.
3. Data preprocessing: handling missing values, imputing median age, one-hot encoding categorical variables.
4. Feature scaling using StandardScaler.
5. Train a Logistic Regression model on 80% training data.
6. Model evaluation on test data: Accuracy, Precision, Recall, F1-Score, ROC-AUC, and Confusion Matrix.
7. Predict survival probability for custom passenger profiles.

SHORT QUESTION:
Full-cycle Titanic classification with Logistic Regression, EDA visualizations, metrics, and custom prediction.

================================================================================
README & SETUP INSTRUCTIONS:
================================================================================
# Question 55: Titanic Logistic Regression Deep Dive

## Requirements
- Python 3.x
- Libraries: pandas, numpy, scikit-learn, matplotlib, seaborn

## How to Run
```bash
python question_55_iris2.py
```

================================================================================
SOURCE CODE FILES:
================================================================================

1. question_55_iris2.py
--------------------------------------------------------------------------------
# ============================================================
# TITANIC DATASET - CLASSIFICATION USING LOGISTIC REGRESSION
# Training, Testing, Evaluation, Plotting and Prediction
# ============================================================

# ------------------------------------------------------------
# 1. Import Libraries
# ------------------------------------------------------------

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.model_selection import (
    train_test_split,
    cross_val_score,
    learning_curve
)
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
    accuracy_score,
    precision_score,
    recall_score,
    f1_score,
    classification_report,
    confusion_matrix,
    ConfusionMatrixDisplay
)


# ------------------------------------------------------------
# 2. Load Titanic Dataset
# ------------------------------------------------------------

# Reads titanic.csv from the current directory, or loads from GitHub if not found
try:
    df_raw = pd.read_csv("titanic.csv")
except FileNotFoundError:
    url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
    df_raw = pd.read_csv(url)

target_names = ["Did Not Survive", "Survived"]

print("Dataset Shape:")
print(df_raw.shape)

print("\nFirst 5 Rows:")
print(df_raw.head())

print("\nDataset Information:")
print(df_raw.info())

print("\nStatistical Summary:")
print(df_raw.describe())


# ------------------------------------------------------------
# 3. Data Cleaning and Feature Selection
# ------------------------------------------------------------

# Select relevant features
features = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare"]
df = df_raw[features + ["Survived"]].copy()

# Check missing values before imputation
print("\nMissing Values Before Imputation:")
print(df.isnull().sum())

# Handle missing values
df["Age"] = df["Age"].fillna(df["Age"].median())
df["Fare"] = df["Fare"].fillna(df["Fare"].median())

# Encode categorical variables: 'Sex' (female: 0, male: 1)
df["Sex"] = df["Sex"].map({"female": 0, "male": 1})

# Define X and y
feature_names = features
X = df[feature_names].values
y = df["Survived"].values

print("\nMissing Values After Imputation:")
print(df.isnull().sum())


# ------------------------------------------------------------
# 4. Exploratory Data Visualization: Correlation Heatmap
# ------------------------------------------------------------

plt.figure(figsize=(8, 6))
sns.heatmap(
    df.corr(),
    annot=True,
    cmap="coolwarm",
    fmt=".2f"
)
plt.title("Titanic Feature Correlation Heatmap")
plt.tight_layout()
plt.show()


# ------------------------------------------------------------
# 5. Train-Test Split
# ------------------------------------------------------------

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.20,
    random_state=42,
    stratify=y
)

print("\nTraining Samples:", len(X_train))
print("Testing Samples:", len(X_test))


# ------------------------------------------------------------
# 6. Feature Scaling
# ------------------------------------------------------------

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)


# ------------------------------------------------------------
# 7. Create and Train Classification Model
# ------------------------------------------------------------

model = LogisticRegression(
    max_iter=200,
    random_state=42
)

model.fit(X_train_scaled, y_train)

print("\nModel Training Completed.")


# ------------------------------------------------------------
# 8. Prediction on Training & Testing Data
# ------------------------------------------------------------

y_train_pred = model.predict(X_train_scaled)
y_test_pred = model.predict(X_test_scaled)


# ------------------------------------------------------------
# 9. Training and Testing Accuracy
# ------------------------------------------------------------

train_accuracy = accuracy_score(y_train, y_train_pred)
test_accuracy = accuracy_score(y_test, y_test_pred)

print("\nTraining Accuracy:")
print(f"{train_accuracy:.4f}")

print("\nTesting Accuracy:")
print(f"{test_accuracy:.4f}")


# ------------------------------------------------------------
# 10. Classification Metrics
# ------------------------------------------------------------

precision = precision_score(y_test, y_test_pred, average="weighted")
recall = recall_score(y_test, y_test_pred, average="weighted")
f1 = f1_score(y_test, y_test_pred, average="weighted")

print("\nEvaluation Metrics")
print("-------------------")
print(f"Accuracy  : {test_accuracy:.4f}")
print(f"Precision : {precision:.4f}")
print(f"Recall    : {recall:.4f}")
print(f"F1 Score  : {f1:.4f}")


# ------------------------------------------------------------
# 11. Classification Report
# ------------------------------------------------------------

print("\nClassification Report")
print("---------------------")
print(
    classification_report(
        y_test,
        y_test_pred,
        target_names=target_names
    )
)


# ------------------------------------------------------------
# 12. Confusion Matrix & Visualization
# ------------------------------------------------------------

cm = confusion_matrix(y_test, y_test_pred)

print("\nConfusion Matrix:")
print(cm)

# Plot Confusion Matrix using ConfusionMatrixDisplay
fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(
    y_test,
    y_test_pred,
    display_labels=target_names,
    cmap="Blues",
    ax=ax
)
plt.title("Confusion Matrix - Logistic Regression (Titanic)")
plt.tight_layout()
plt.show()


# ------------------------------------------------------------
# 13. Actual vs Predicted Classes
# ------------------------------------------------------------

comparison = pd.DataFrame({
    "Actual": y_test,
    "Predicted": y_test_pred
})

comparison["Actual"] = comparison["Actual"].map(dict(enumerate(target_names)))
comparison["Predicted"] = comparison["Predicted"].map(dict(enumerate(target_names)))

print("\nActual vs Predicted (First 20 Samples):")
print(comparison.head(20))


# ------------------------------------------------------------
# 14. Cross Validation
# ------------------------------------------------------------

cv_scores = cross_val_score(
    model,
    X_train_scaled,
    y_train,
    cv=5,
    scoring="accuracy"
)

print("\nCross Validation Accuracy Scores:")
print(cv_scores)

print(f"\nMean CV Accuracy   : {cv_scores.mean():.4f}")
print(f"Standard Deviation : {cv_scores.std():.4f}")


# ------------------------------------------------------------
# 15. Check Overfitting / Underfitting
# ------------------------------------------------------------

difference = train_accuracy - test_accuracy

print("\nModel Fit Analysis")
print("------------------")
print(f"Training Accuracy : {train_accuracy:.4f}")
print(f"Testing Accuracy  : {test_accuracy:.4f}")
print(f"Difference        : {difference:.4f}")

if train_accuracy < 0.70 and test_accuracy < 0.70:
    print("\nPossible Underfitting")
elif train_accuracy > 0.90 and difference > 0.10:
    print("\nPossible Overfitting")
else:
    print("\nNo strong indication of overfitting or underfitting")


# ------------------------------------------------------------
# 16. Learning Curve
# ------------------------------------------------------------

train_sizes, train_scores, validation_scores = learning_curve(
    model,
    X_train_scaled,
    y_train,
    cv=5,
    scoring="accuracy",
    train_sizes=np.linspace(0.1, 1.0, 10),
    random_state=42
)

train_mean = train_scores.mean(axis=1)
validation_mean = validation_scores.mean(axis=1)

plt.figure(figsize=(8, 5))
plt.plot(
    train_sizes,
    train_mean,
    marker="o",
    label="Training Accuracy"
)
plt.plot(
    train_sizes,
    validation_mean,
    marker="o",
    label="Validation Accuracy"
)
plt.xlabel("Number of Training Samples")
plt.ylabel("Accuracy")
plt.title("Learning Curve - Titanic Classification")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()


# ------------------------------------------------------------
# 17. Predict a Single Passenger Sample
# ------------------------------------------------------------

# Example passenger: Pclass=3, Sex=1 (male), Age=22.0, SibSp=1, Parch=0, Fare=7.25
sample = np.array([
    [3, 1, 22.0, 1, 0, 7.25]
])

# Scale the sample
sample_scaled = scaler.transform(sample)

# Make prediction and probability
prediction = model.predict(sample_scaled)
probability = model.predict_proba(sample_scaled)

print("\nIndividual Prediction")
print("---------------------")
print("Predicted Class:", target_names[prediction[0]])
print("\nPrediction Probabilities:")
for name, prob in zip(target_names, probability[0]):
    print(f"{name}: {prob:.4f}")


# ------------------------------------------------------------
# 18. User Input Prediction
# ------------------------------------------------------------

print("\nEnter Passenger Details for Prediction")
try:
    pclass = int(input("Passenger Class (1, 2, or 3): "))
    sex_input = input("Sex (female/male): ").strip().lower()
    sex = 0 if sex_input == "female" else 1
    age = float(input("Age (in years): "))
    sibsp = int(input("Number of Siblings/Spouses Aboard: "))
    parch = int(input("Number of Parents/Children Aboard: "))
    fare = float(input("Passenger Fare (e.g. 32.20): "))

    user_sample = np.array([[pclass, sex, age, sibsp, parch, fare]])
    user_sample_scaled = scaler.transform(user_sample)

    user_pred = model.predict(user_sample_scaled)
    user_prob = model.predict_proba(user_sample_scaled)

    print("\nPrediction Result")
    print("-----------------")
    print("Predicted Status:", target_names[user_pred[0]])
    print("\nClass Probabilities:")
    for name, prob in zip(target_names, user_prob[0]):
        print(f"{name}: {prob:.4f}")
except Exception as e:
    print(f"Skipping interactive user input due to error/environment constraints: {e}")
--------------------------------------------------------------------------------
