Predict Titanic Survivors with Python
Build a logistic regression model to predict Titanic survival from real data.
Introduction
30 Second Summary
When the Titanic sank in 1912, whether a passenger survived depended on factors like their ticket class, gender, and age. Machine learning can find these hidden patterns in data automatically.
In this project, you will build a machine learning model that predicts Titanic passenger survival using Python. You will take raw, messy passenger data through the full data science workflow: exploring, cleaning, visualizing, and modeling.
What You'll Build
A Jupyter notebook that loads 891 Titanic passenger records, visualizes survival patterns, cleans real-world data issues, and trains a logistic regression model to predict who survived.
By the end of this project, you'll have:
- A working machine learning model that predicts Titanic passenger survival with approximately 80% accuracy.
- A complete Jupyter notebook documenting your data science workflow from raw data to predictions.
- Hands-on experience with pandas, matplotlib, and scikit-learn working together on a real dataset.
- Secret Mission: Improve your model's accuracy by engineering new features from existing data.
Are there any prerequisites?
You need basic Python syntax knowledge (variables, functions, loops). No prior data science or machine learning experience is required.
Before We Start
Before We Start...
Set Up Jupyter in VS Code
Before you can build a machine learning model, you need a place to write and run code interactively. Jupyter notebooks let you run code in small pieces (called cells) and see results immediately below each piece.
In this step, you'll set up a Jupyter notebook inside VS Code. This gives you an interactive coding environment without needing to install a separate application.
In this step, get ready to:
- Install ipykernel so VS Code can run Jupyter notebooks.
- Create and save your project notebook file.
- Test that all required libraries load correctly.
Install the ipykernel package
The ipykernel package provides the Python kernel that VS Code needs to execute code inside Jupyter notebooks. Without it, VS Code can open notebook files but cannot run any cells.
- Open the integrated terminal in VS Code by pressing Ctrl+` (backtick key, above Tab on your keyboard).
- Install ipykernel by running this command:
pip install ipykernel
- Verify ipykernel installed correctly by running:
python -c "import ipykernel; print(ipykernel.__version__)"
You should see a version number printed below the command. This confirms ipykernel is ready.
Seeing 'pip: command not found'?
Try pip3 instead of pip. On some systems, Python 3's package manager is named pip3.
If you see a permissions error, try adding --user to the end of the install command.
Help me troubleshoot this
Create your project notebook
A Jupyter notebook is a file (ending in .ipynb) that combines code cells, their output, and text notes in a single document. You will use one notebook for this entire project.
- Open the Command Palette by pressing Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows).
- Type Create: New Jupyter Notebook and select it from the dropdown.
VS Code opens a new untitled notebook file with an empty code cell ready for input.
- In the top-right corner of the notebook, click Select Kernel.
- Choose your Python 3.14.3 interpreter from the list.
- Save the file by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Name the file titanic_survival.ipynb and save it to your Desktop.
Why the Desktop?
Saving to your Desktop makes the file easy to find later. You can move it to a different folder after the project if you prefer a different organisation.
Don't see Python 3.14.3 in the kernel list?
Make sure you installed ipykernel in the previous substep. The kernel list only shows Python environments that have ipykernel installed.
If you see multiple Python versions listed, pick the one labelled 3.14.3. If none appear, close and reopen VS Code, then try selecting the kernel again.
Help me fix my kernel picker
Test your setup by running a cell
This first cell imports every library you will use throughout the project. Running it now confirms your environment is set up correctly before you start working with data.
- Click inside the first empty cell in your notebook, then paste the following code:
# Cell 1: Import libraries
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
print("All libraries loaded successfully!")
print(f"pandas version: {pd.__version__}")
- Run the cell by pressing Shift+Enter.
What does this code do?
- pandas (imported as pd) loads and manipulates tabular data. Think of it as Excel in code.
- matplotlib (imported as plt) creates charts and visualizations.
- LogisticRegression is the machine learning algorithm you will train to make predictions.
- train_test_split divides your data into a learning set and an evaluation set.
- accuracy_score and classification_report measure how well your model performs.
You should see output directly below the cell showing All libraries loaded successfully! followed by your pandas version number.
Seeing a ModuleNotFoundError?
If the error mentions pandas, matplotlib, or sklearn, that library is not installed in the Python environment your kernel is using.
Run the appropriate install command in your terminal:
pip install pandas matplotlib scikit-learn
After installing, restart your kernel by clicking the restart icon in the notebook toolbar, then run the cell again.
Help me fix this import error
✔️ Awesome, I've got everything!
Great. Make sure you have saved titanic_survival.ipynb before moving on.
ⓧ I'd like to double check the full code
Your notebook should have exactly one cell with this code:
# Cell 1: Import libraries
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
print("All libraries loaded successfully!")
print(f"pandas version: {pd.__version__}")
Your environment is ready. Next up, you'll load real passenger data from the Titanic and start exploring who survived.
Load and Explore the Data
Your notebook is set up and all libraries are imported. Now it is time to get some data to work with.
In this step, you will load the Titanic passenger dataset into a pandas DataFrame and explore its structure. Your goal is to understand what information is available and identify problems in the data before building a model.
In this step, get ready to:
- Load 891 Titanic passenger records from a CSV file into a DataFrame.
- Identify missing values and understand what data you are working with.
- Generate summary statistics to understand the numbers.
Load the Titanic dataset into a DataFrame
The Titanic dataset is a CSV file hosted on GitHub. It contains one row per passenger with details like their name, age, ticket class, and whether they survived.
- In your notebook, click into a new cell below Cell 1 and load the dataset by typing the following code:
# Cell 2: Load the dataset
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()
What does this code do?
The url variable stores the web address where the CSV file lives.
The pd.read_csv(url) function downloads the CSV and loads it into a DataFrame called df. A DataFrame is like a spreadsheet in Python. Each row is a passenger and each column is a piece of information about them.
The df.head() call shows the first 5 rows so you can quickly see what the data looks like.
- Press Shift+Enter to run the cell.
- You should see Dataset shape: (891, 12) printed, followed by a table showing the first 5 passengers with columns like Name, Sex, Age, and Survived.
Seeing a connection error?
The dataset loads from a URL, so you need an internet connection. If you see a network error, check that you are connected to the internet and try running the cell again.
If you see a NameError: name 'pd' is not defined error, go back to Cell 1 and run it first. The imports must execute before you can use pd.read_csv().
help me fix a connection or NameError when loading the Titanic CSV
Explore the data structure and identify problems
Before building a model, you need to know what you are working with. Real-world data almost always has problems like missing values or inconsistent formats.
- In the next empty cell, explore the data structure by typing the following code:
# Cell 3: Explore data structure
print(df.info())
print(f"\nMissing values:\n{df.isnull().sum()}")
print(f"\nSurvival distribution:\n{df['Survived'].value_counts()}")
What does this code do?
- df.info() shows each column's name, how many non-null values it has, and its data type.
- df.isnull().sum() counts the missing values in each column. This tells you what needs cleaning later.
- df['Survived'].value_counts() counts how many passengers survived (1) versus died (0). This is your prediction target.
- Press Shift+Enter to run the cell.
- You should see that Age has 177 missing values, Cabin has 687 missing values, and Embarked has 2 missing values.
- The Survived column shows 549 passengers died (0) and 342 survived (1).
Why do missing values matter?
Machine learning models cannot handle blank cells. Before training a model, you will need to decide what to do with these gaps. You will handle this in a later step.
The Cabin column is missing 687 out of 891 values, so it is mostly empty. You will skip it entirely. Age is missing 177 values, which you will fill in with a reasonable estimate.
Seeing KeyError for Survived?
Make sure you type Survived with a capital S. Column names are case-sensitive in pandas.
help me fix a KeyError when accessing a DataFrame column
Generate summary statistics
Summary statistics give you a quick numerical overview of every column. This helps you spot patterns and outliers at a glance.
- In the next empty cell, generate summary statistics by typing the following code:
# Cell 4: Summary statistics
df.describe()
What should I see?
The df.describe() output shows count, mean, standard deviation, min, max, and quartiles for every numeric column.
Key things to notice: the mean of Survived is about 0.38, meaning only 38% of passengers survived. The average age is about 29. Fares range wildly from 0 to 512, showing huge differences in ticket prices.
- Press Shift+Enter to run the cell.
- You should see a table with statistics for columns like Survived, Pclass, Age, SibSp, Parch, and Fare.
✔️ Awesome, I've got everything!
Great. Make sure you have saved your notebook before moving on.
ⓧ I'd like to double check the full code
Here is the full notebook up to this point. Each section separated by a comment is a separate cell.
# Cell 1: Import libraries
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
print("All libraries loaded successfully!")
print(f"pandas version: {pd.__version__}")
# Cell 2: Load the dataset
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()
# Cell 3: Explore data structure
print(df.info())
print(f"\nMissing values:\n{df.isnull().sum()}")
print(f"\nSurvival distribution:\n{df['Survived'].value_counts()}")
# Cell 4: Summary statistics
df.describe()
You now know your dataset inside and out. You have 891 passengers, 12 columns, and some messy missing data to deal with. Next up, you will create visualizations that reveal who had the best chance of survival.
Visualize Survival Patterns
In the previous step, you loaded the Titanic dataset and explored its structure. You now know there are 891 passengers, missing values in several columns, and that only 38% survived.
Numbers alone do not tell a compelling story. In this step, you will create three visualizations that reveal clear survival patterns by gender, class, and age. These charts will guide which features you feed into your machine learning model later.
In this step, get ready to:
- Chart survival rate by gender to see the "women and children first" effect.
- Chart survival rate by ticket class to see how wealth affected outcomes.
- Plot age distributions split by survival to spot age-related patterns.
Create a bar chart of survival rate by gender
Your exploration showed 342 survivors and 549 non-survivors. Grouping by gender reveals a dramatic split in who lived and who died.
- Click the + Code button at the top of your notebook to create a new cell (Cell 5).
- Type the following code and press Shift+Enter to run it:
# Cell 5: Survival by gender
survival_by_sex = df.groupby('Sex')['Survived'].mean()
plt.figure(figsize=(8, 5))
survival_by_sex.plot(kind='bar', color=['steelblue', 'coral'])
plt.title('Survival Rate by Gender')
plt.ylabel('Survival Rate')
plt.xlabel('Gender')
plt.xticks(rotation=0)
plt.ylim(0, 1)
plt.tight_layout()
plt.show()
print(f"Female survival rate: {survival_by_sex['female']:.1%}")
print(f"Male survival rate: {survival_by_sex['male']:.1%}")
What does this code do?
The groupby('Sex')['Survived'].mean() line groups all passengers by gender, then calculates the average of the Survived column (where 1 = survived, 0 = died). The average becomes the survival rate.
The matplotlib calls build a bar chart with labeled axes and a y-axis from 0 to 1 (representing 0% to 100%).
You should see a bar chart with two bars and printed text showing approximately 74% female survival and 19% male survival.
Don't see a chart?
Make sure you ran all previous cells first (Cells 1-4). The df variable must already exist from Cell 2.
If you see a NameError, click Run All at the top of the notebook to re-execute every cell in order.
help me debug why my chart isn't showing
Visualize survival by passenger class
The Titanic had three ticket classes. First class passengers had cabins on the upper decks, closer to lifeboats. Your chart will show whether that proximity translated to survival.
- Create a new cell (Cell 6) and run the following code:
# Cell 6: Survival by class
survival_by_class = df.groupby('Pclass')['Survived'].mean()
plt.figure(figsize=(8, 5))
survival_by_class.plot(kind='bar', color=['gold', 'silver', 'sienna'])
plt.title('Survival Rate by Ticket Class')
plt.ylabel('Survival Rate')
plt.xlabel('Class (1=First, 2=Second, 3=Third)')
plt.xticks(rotation=0)
plt.ylim(0, 1)
plt.tight_layout()
plt.show()
What does this code do?
This follows the same pattern as the gender chart. The groupby('Pclass') groups passengers by their ticket class (1, 2, or 3), then calculates the mean survival rate for each group.
The gold, silver, and sienna colors match the class hierarchy visually. First class survived at approximately 63%, second class at 47%, and third class at only 24%.
You should see three bars with a clear downward trend from first class to third class.
Seeing a blank output?
Check that you included plt.show() at the end of the cell. Without it, some environments will not render the figure.
help me fix my class survival chart
Create an age distribution histogram split by survival
Gender and class told a clear story. Age is more nuanced. Instead of a simple bar chart, you will overlay two histograms on top of each other to see how the age distributions differ between survivors and non-survivors.
- Create a new cell (Cell 7) and run the following code:
# Cell 7: Age distribution by survival
plt.figure(figsize=(10, 5))
df[df['Survived'] == 1]['Age'].hist(alpha=0.5, bins=30, label='Survived', color='green')
df[df['Survived'] == 0]['Age'].hist(alpha=0.5, bins=30, label='Did Not Survive', color='red')
plt.title('Age Distribution by Survival')
plt.xlabel('Age')
plt.ylabel('Count')
plt.legend()
plt.tight_layout()
plt.show()
What does this code do?
The df[df['Survived'] == 1] filtering creates a subset of only survivors, then .hist() plots their age distribution. The same is done for non-survivors.
Setting alpha=0.5 makes each histogram semi-transparent so you can see where they overlap. The bins=30 parameter splits the age range into 30 buckets for a detailed view.
You should see two overlapping distributions. Notice how young children (ages 0-5) have more green than red, indicating higher survival rates.
Chart looks strange or empty?
Remember that Age has 177 missing values. The histogram simply skips those rows. You will handle these missing values in the next step when preparing data for the model.
If you see no output at all, confirm that Cell 1 imported matplotlib.pyplot successfully.
help me debug my age histogram
✔️ Awesome, I've got everything!
Great. Double check you have all three charts rendered (Cells 5, 6, and 7) before moving on.
ⓧ I'd like to double check the full code
Here is the complete notebook up to this point. Compare your cells against this:
# Cell 1: Import libraries
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
print("All libraries loaded successfully!")
print(f"pandas version: {pd.__version__}")
# Cell 2: Load the dataset
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()
# Cell 3: Explore data structure
print(df.info())
print(f"\nMissing values:\n{df.isnull().sum()}")
print(f"\nSurvival distribution:\n{df['Survived'].value_counts()}")
# Cell 4: Summary statistics
df.describe()
# Cell 5: Survival by gender
survival_by_sex = df.groupby('Sex')['Survived'].mean()
plt.figure(figsize=(8, 5))
survival_by_sex.plot(kind='bar', color=['steelblue', 'coral'])
plt.title('Survival Rate by Gender')
plt.ylabel('Survival Rate')
plt.xlabel('Gender')
plt.xticks(rotation=0)
plt.ylim(0, 1)
plt.tight_layout()
plt.show()
print(f"Female survival rate: {survival_by_sex['female']:.1%}")
print(f"Male survival rate: {survival_by_sex['male']:.1%}")
# Cell 6: Survival by class
survival_by_class = df.groupby('Pclass')['Survived'].mean()
plt.figure(figsize=(8, 5))
survival_by_class.plot(kind='bar', color=['gold', 'silver', 'sienna'])
plt.title('Survival Rate by Ticket Class')
plt.ylabel('Survival Rate')
plt.xlabel('Class (1=First, 2=Second, 3=Third)')
plt.xticks(rotation=0)
plt.ylim(0, 1)
plt.tight_layout()
plt.show()
# Cell 7: Age distribution by survival
plt.figure(figsize=(10, 5))
df[df['Survived'] == 1]['Age'].hist(alpha=0.5, bins=30, label='Survived', color='green')
df[df['Survived'] == 0]['Age'].hist(alpha=0.5, bins=30, label='Did Not Survive', color='red')
plt.title('Age Distribution by Survival')
plt.xlabel('Age')
plt.ylabel('Count')
plt.legend()
plt.tight_layout()
plt.show()
Your three visualizations now tell a clear story: gender, class, and age all influenced survival on the Titanic. Next up, you will clean the messy data and transform it into a format your machine learning model can understand.
Clean and Prepare Data for the Model
Your visualizations told a clear story: gender, class, and age all influenced who survived the Titanic. Now you need to translate those insights into a format a machine learning model can actually work with.
Real-world data is messy. Your dataset has 177 missing age values, and the Sex column contains text instead of numbers. In this step, you will fix both problems and split the data into training and testing sets.
In this step, get ready to:
- Fill missing values and select which features the model will use.
- Convert text columns to numbers so the model can process them.
- Split the data into a training set and a test set.
Handle missing values and select features
Machine learning models cannot work with empty cells. You need to fill the 177 missing age values with a reasonable estimate before the model can use that column.
- Click the + Code button at the top of your notebook to create a new cell (Cell 8).
- Fill missing ages, select features, and create a working DataFrame by pasting this code:
# Cell 8: Handle missing values and select features
df['Age'] = df['Age'].fillna(df['Age'].median())
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare']
model_df = df[features + ['Survived']].copy()
print(f"Missing values after cleaning:\n{model_df.isnull().sum()}")
What does this code do?
fillna(df['Age'].median()) replaces every missing age with the median age of all passengers. The median is more robust than the mean because extreme values (like an 80-year-old) do not skew it.
The features list defines which columns the model will use as inputs. You are deliberately excluding columns like Cabin (687 missing values) and Ticket (unstructured text) because they would add noise without helping predictions.
- Press Shift+Enter to run the cell.
- You should see all columns reporting 0 missing values in the output.
Still seeing missing values?
Make sure your Cell 8 runs after the cells that load the dataset. If you restarted the kernel, re-run all cells from Cell 1 onward by clicking Run All at the top of the notebook.
Help me debug missing values in my DataFrame.
Convert categorical text to numbers
The Sex column currently contains the words female and male. Logistic regression only understands numbers, so you need to encode the text as 1 and 0.
- Create a new cell (Cell 9) and convert the Sex column to numbers by pasting this code:
# Cell 9: Convert categorical to numeric
model_df['Sex'] = model_df['Sex'].map({'female': 1, 'male': 0})
print(f"\nCleaned data sample:")
model_df.head()
What does this code do?
.map({'female': 1, 'male': 0}) replaces every occurrence of female with 1 and male with 0. This encoding means the model's coefficient for Sex will directly reflect the survival advantage of being female.
- Press Shift+Enter to run the cell.
- The output table should show the Sex column containing only 1 and 0 instead of text. Every column is now numeric.
Seeing NaN values in the Sex column?
If you ran Cell 9 twice, the second run maps integers (not strings) and produces NaN. Re-run Cell 8 first to reset model_df with fresh text values, then run Cell 9 once.
Help me fix NaN in my Sex column after mapping.
Split data into training and test sets
You need to hold back some data that the model never sees during training. This unseen data becomes your test set, the only fair way to measure how well the model generalizes to new passengers.
- Create a new cell (Cell 10) and split your data into training and test sets by pasting this code:
# Cell 10: Train/test split
X = model_df.drop('Survived', axis=1)
y = model_df['Survived']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"Training set size: {X_train.shape[0]} passengers")
print(f"Test set size: {X_test.shape[0]} passengers")
What does this code do?
X holds the input features (everything except Survived). y holds the target label (Survived). The model learns the relationship between X and y.
train_test_split randomly shuffles the data and puts 80% into training and 20% into testing. random_state=42 locks the shuffle so you get the same split every time you run it. This makes your results reproducible.
- Press Shift+Enter to run the cell.
- You should see Training set size: 712 passengers and Test set size: 179 passengers in the output.
Seeing a NameError for train_test_split?
This means Cell 1 (where you imported train_test_split) was not run in this kernel session. Click Run All at the top of the notebook to re-run every cell from the beginning.
Help me fix a NameError in my notebook.
Why 80/20 and not 50/50?
The model needs as much training data as possible to learn meaningful patterns. An 80/20 split is the standard starting point in data science. It gives the model 712 examples to learn from while keeping 179 unseen passengers for an honest accuracy check.
✔️ Awesome, I've got everything!
Great. Make sure you have saved your notebook before moving on.
ⓧ I'd like to double check the full code
Here is everything your notebook should contain at this point. Compare cell by cell:
# Cell 1: Import libraries
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
print("All libraries loaded successfully!")
print(f"pandas version: {pd.__version__}")
# Cell 2: Load the dataset
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()
# Cell 3: Explore data structure
print(df.info())
print(f"\nMissing values:\n{df.isnull().sum()}")
print(f"\nSurvival distribution:\n{df['Survived'].value_counts()}")
# Cell 4: Summary statistics
df.describe()
# Cell 5: Survival by gender
survival_by_sex = df.groupby('Sex')['Survived'].mean()
plt.figure(figsize=(8, 5))
survival_by_sex.plot(kind='bar', color=['steelblue', 'coral'])
plt.title('Survival Rate by Gender')
plt.ylabel('Survival Rate')
plt.xlabel('Gender')
plt.xticks(rotation=0)
plt.ylim(0, 1)
plt.tight_layout()
plt.show()
print(f"Female survival rate: {survival_by_sex['female']:.1%}")
print(f"Male survival rate: {survival_by_sex['male']:.1%}")
# Cell 6: Survival by class
survival_by_class = df.groupby('Pclass')['Survived'].mean()
plt.figure(figsize=(8, 5))
survival_by_class.plot(kind='bar', color=['gold', 'silver', 'sienna'])
plt.title('Survival Rate by Ticket Class')
plt.ylabel('Survival Rate')
plt.xlabel('Class (1=First, 2=Second, 3=Third)')
plt.xticks(rotation=0)
plt.ylim(0, 1)
plt.tight_layout()
plt.show()
# Cell 7: Age distribution by survival
plt.figure(figsize=(10, 5))
df[df['Survived'] == 1]['Age'].hist(alpha=0.5, bins=30, label='Survived', color='green')
df[df['Survived'] == 0]['Age'].hist(alpha=0.5, bins=30, label='Did Not Survive', color='red')
plt.title('Age Distribution by Survival')
plt.xlabel('Age')
plt.ylabel('Count')
plt.legend()
plt.tight_layout()
plt.show()
# Cell 8: Handle missing values and select features
df['Age'] = df['Age'].fillna(df['Age'].median())
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare']
model_df = df[features + ['Survived']].copy()
print(f"Missing values after cleaning:\n{model_df.isnull().sum()}")
# Cell 9: Convert categorical to numeric
model_df['Sex'] = model_df['Sex'].map({'female': 1, 'male': 0})
print(f"\nCleaned data sample:")
model_df.head()
# Cell 10: Train/test split
X = model_df.drop('Survived', axis=1)
y = model_df['Survived']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"Training set size: {X_train.shape[0]} passengers")
print(f"Test set size: {X_test.shape[0]} passengers")
Your data is cleaned, encoded, and split. Next up, you will train a logistic regression model on these 712 passengers and find out how accurately it predicts survival.
Build and Evaluate Your Prediction Model
Your data is clean, numeric, and split into training and test sets. The hard part of data preparation is done.
Now comes the payoff. You will train a logistic regression model on the 712 training passengers, then test its predictions against the 179 passengers it has never seen. Finally, you will look inside the model to understand which features actually mattered for survival.
In this step, get ready to:
- Train a logistic regression model on your prepared training data.
- Evaluate predictions with an accuracy score and classification report.
- Interpret feature coefficients to understand what drove survival.
Train a logistic regression model
Logistic regression learns a probability function that maps your six numeric features to a survival prediction (0 or 1). Training means the algorithm finds the best weight for each feature by looking at the 712 training passengers.
- Create a new cell below Cell 10 and train your model by running this code:
# Cell 11: Train the model
model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)
print("Model trained successfully!")
What does this code do?
LogisticRegression(max_iter=200) creates the model object. The max_iter=200 setting gives the algorithm enough iterations to find optimal weights for your unscaled features. The default of 100 can sometimes stop too early.
model.fit(X_train, y_train) is where the actual learning happens. The model examines all 712 training passengers and adjusts its internal weights until it can best predict their survival outcomes.
- Press Shift+Enter to run the cell.
- You should see Model trained successfully! printed below the cell.
Seeing a ConvergenceWarning?
If you see a warning about convergence, double-check that your max_iter is set to 200, not the default 100. The model needs extra iterations to find optimal weights when features have different scales.
Help me fix my logistic regression convergence warning
Make predictions and measure accuracy
The real test of any model is how well it performs on data it has never seen. Your test set of 179 passengers was held back during training specifically for this moment.
- Generate predictions and evaluate your model's performance by running this code in a new cell:
# Cell 12: Evaluate the model
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {accuracy:.1%}")
print(f"\nDetailed Performance Report:")
print(classification_report(y_test, y_pred, target_names=['Did Not Survive', 'Survived']))
What does this code do?
- model.predict(X_test) feeds the 179 test passengers through the trained model. Each passenger gets a prediction: 0 (did not survive) or 1 (survived).
- accuracy_score(y_test, y_pred) compares predictions against the actual outcomes and returns the percentage that matched.
- classification_report breaks down performance per class, showing precision (of those predicted to survive, how many actually did) and recall (of those who actually survived, how many did we catch).
- Press Shift+Enter to run the cell.
- You should see an accuracy score between 78% and 82%, followed by the detailed classification report table.
Is 80% accuracy good?
For a first model on messy real-world data with no feature engineering, 80% is solid. Remember that only 38% of passengers survived. A model that always guessed "did not survive" would already be 62% accurate. Your model beats that naive baseline by a significant margin.
Seeing a NameError?
If you get a NameError for model, X_test, or y_test, make sure you ran Cell 10 and Cell 11 first. Jupyter cells must be executed in order for variables to exist in memory.
Help me debug a NameError in my Jupyter notebook
Interpret what the model learned
Unlike black-box models, logistic regression gives you a coefficient for each feature. A positive coefficient means that feature increased survival probability. A negative one means it decreased it.
- Examine and visualize feature importance by running this code in a new cell:
# Cell 13: Feature importance
feature_importance = pd.DataFrame({
'Feature': features,
'Coefficient': model.coef_[0]
}).sort_values('Coefficient', key=abs, ascending=False)
print("Feature Importance (how much each feature affects survival):")
print(feature_importance.to_string(index=False))
plt.figure(figsize=(8, 5))
plt.barh(feature_importance['Feature'], feature_importance['Coefficient'])
plt.title('What Mattered Most for Survival?')
plt.xlabel('Coefficient (positive = helped survival)')
plt.tight_layout()
plt.show()
What does this code do?
- model.coef_[0] extracts the learned weight for each feature. These are the numbers the model multiplied each feature by during prediction.
- sort_values('Coefficient', key=abs, ascending=False) sorts features by the absolute size of their effect, so the most influential features appear first.
- plt.barh() creates a horizontal bar chart. Bars extending right (positive) mean the feature helped survival. Bars extending left (negative) mean it hurt survival.
- Press Shift+Enter to run the cell.
- You should see a printed table of coefficients and a horizontal bar chart. Sex has the largest positive coefficient (being female greatly increased survival). Pclass has a negative coefficient (higher class number means lower class, which meant less chance of survival).
Why is this chart important?
Feature importance is how data scientists explain their models to non-technical stakeholders. Instead of saying "the model is 80% accurate," you can say "gender and ticket class were the two strongest predictors of survival." This interpretability is one reason logistic regression remains popular despite fancier alternatives.
Chart not displaying?
If you see a text representation instead of a chart, make sure your kernel is set to Python 3.14.3 in the top-right corner of VS Code. Also confirm that matplotlib imported without errors back in Cell 1.
Help me get matplotlib charts to display in VS Code Jupyter
Save your notebook by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Double check you have saved your notebook with all 13 cells executed.
ⓧ I'd like to double check the full code
Here is the full notebook from Cell 1 through Cell 13. Compare each cell with yours to find any differences.
# Cell 1: Import libraries
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
print("All libraries loaded successfully!")
print(f"pandas version: {pd.__version__}")
# Cell 2: Load the dataset
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()
# Cell 3: Explore data structure
print(df.info())
print(f"\nMissing values:\n{df.isnull().sum()}")
print(f"\nSurvival distribution:\n{df['Survived'].value_counts()}")
# Cell 4: Summary statistics
df.describe()
# Cell 5: Survival by gender
survival_by_sex = df.groupby('Sex')['Survived'].mean()
plt.figure(figsize=(8, 5))
survival_by_sex.plot(kind='bar', color=['steelblue', 'coral'])
plt.title('Survival Rate by Gender')
plt.ylabel('Survival Rate')
plt.xlabel('Gender')
plt.xticks(rotation=0)
plt.ylim(0, 1)
plt.tight_layout()
plt.show()
print(f"Female survival rate: {survival_by_sex['female']:.1%}")
print(f"Male survival rate: {survival_by_sex['male']:.1%}")
# Cell 6: Survival by class
survival_by_class = df.groupby('Pclass')['Survived'].mean()
plt.figure(figsize=(8, 5))
survival_by_class.plot(kind='bar', color=['gold', 'silver', 'sienna'])
plt.title('Survival Rate by Ticket Class')
plt.ylabel('Survival Rate')
plt.xlabel('Class (1=First, 2=Second, 3=Third)')
plt.xticks(rotation=0)
plt.ylim(0, 1)
plt.tight_layout()
plt.show()
# Cell 7: Age distribution by survival
plt.figure(figsize=(10, 5))
df[df['Survived'] == 1]['Age'].hist(alpha=0.5, bins=30, label='Survived', color='green')
df[df['Survived'] == 0]['Age'].hist(alpha=0.5, bins=30, label='Did Not Survive', color='red')
plt.title('Age Distribution by Survival')
plt.xlabel('Age')
plt.ylabel('Count')
plt.legend()
plt.tight_layout()
plt.show()
# Cell 8: Handle missing values and select features
df['Age'] = df['Age'].fillna(df['Age'].median())
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare']
model_df = df[features + ['Survived']].copy()
print(f"Missing values after cleaning:\n{model_df.isnull().sum()}")
# Cell 9: Convert categorical to numeric
model_df['Sex'] = model_df['Sex'].map({'female': 1, 'male': 0})
print(f"\nCleaned data sample:")
model_df.head()
# Cell 10: Train/test split
X = model_df.drop('Survived', axis=1)
y = model_df['Survived']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"Training set size: {X_train.shape[0]} passengers")
print(f"Test set size: {X_test.shape[0]} passengers")
# Cell 11: Train the model
model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)
print("Model trained successfully!")
# Cell 12: Evaluate the model
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {accuracy:.1%}")
print(f"\nDetailed Performance Report:")
print(classification_report(y_test, y_pred, target_names=['Did Not Survive', 'Survived']))
# Cell 13: Feature importance
feature_importance = pd.DataFrame({
'Feature': features,
'Coefficient': model.coef_[0]
}).sort_values('Coefficient', key=abs, ascending=False)
print("Feature Importance (how much each feature affects survival):")
print(feature_importance.to_string(index=False))
plt.figure(figsize=(8, 5))
plt.barh(feature_importance['Feature'], feature_importance['Coefficient'])
plt.title('What Mattered Most for Survival?')
plt.xlabel('Coefficient (positive = helped survival)')
plt.tight_layout()
plt.show()
Secret mission
Improve Accuracy with Feature Engineering
Your model is performing well, but raw data is rarely optimal. By combining and transforming existing columns, you can reveal patterns the model could not see before. Can you push your accuracy higher with feature engineering?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources for future reference or delete them entirely.
Resources you used:
- titanic_survival.ipynb notebook file (local).
- ipykernel Python package (installed via pip).
Keep everything running
No action needed. Choose this if you plan to continue experimenting with the notebook or want to reference your work later.
- Your titanic_survival.ipynb file stays on your machine for future reference.
- The ipykernel package remains installed for any future Jupyter notebooks in VS Code.
Pause - I'll come back to this later
There are no running processes or servers to pause. Your notebook and package sit idle on your machine without consuming resources.
- Close the titanic_survival.ipynb tab in VS Code to free up editor memory.
- Everything will be exactly as you left it when you reopen the file.
Delete - I don't want to use this again
Remove the project file and uninstall the package you added during this project.
- Delete the notebook file by running this in your terminal:
rm titanic_survival.ipynb
- Uninstall ipykernel by running:
pip uninstall ipykernel -y
- Your pre-existing packages (pandas, matplotlib, scikit-learn) were not installed by this project. They remain on your system.
Nice Work!
Nice Work!
You just built a machine learning model that predicts Titanic passenger survival from raw, messy data. This is the exact workflow data scientists use every day: load, explore, clean, visualize, model, evaluate.
You've learned how to:
- Built a logistic regression model that predicts Titanic survival with ~80% accuracy using scikit-learn.
- Created data visualizations with matplotlib that revealed how gender, class, and age influenced survival.
- Cleaned real-world data by filling missing values and converting categorical text to numbers using pandas.
- Secret Mission: Engineered new features (FamilySize, IsAlone, Title) to improve model performance.
Ready to quiz yourself?