End-to-End ML & DL Project · Netflix Shows Dataset

Netflix Shows:
ML vs
Deep Learning

A complete walkthrough of the Netflix Shows dataset from Kaggle. Build 5 scikit-learn models and 2 TensorFlow neural networks to predict release year (regression) and classify Movie vs TV Show. Every line of code is explained for beginners — with copy buttons on every block.

Start the Walkthrough View Dataset
~8,800Shows & movies
12Columns
5ML models / task
2DL networks
load_dataset.py
# Netflix Shows — KaggleHub
import kagglehub
from kagglehub import KaggleDatasetAdapter

df = kagglehub.load_dataset(
    KaggleDatasetAdapter.PANDAS,
    "shivamb/netflix-shows",
    file_path,
)

print("First 5 records:", df.head())
5 ML + 2 DLmodels trained
Copy & Runfull project code
Movie vs TVclassification task
Step 01 / Data Loading

Load the dataset with KaggleHub.

Stream the Netflix Shows dataset straight into a pandas DataFrame — no manual download needed.

🧑‍🏫 Beginner’s Guide — Before You Start

What is this dataset? The Netflix Shows dataset on Kaggle contains information about all movies and TV shows available on Netflix. Each row is one title, with columns for: show_id, type (Movie or TV Show), title, director, cast, country, date_added, release_year, rating, duration, listed_in (genres), and description. There are roughly 8,800 rows.

What is KaggleHub? Kaggle is a website where people share datasets for machine learning. KaggleHub is a Python library that lets you load those datasets directly into your code with one line — no manual downloading.

What is a DataFrame? A pandas DataFrame is like an Excel spreadsheet inside Python. It has rows (one per Netflix title) and columns (type, rating, duration, etc.). The variable df we create here holds all ~8,800 shows.

Why check shape, head(), info(), describe()?

  • df.shape → How many rows (titles) and columns?
  • df.head() → Show the first 5 rows so you can see column names and sample values
  • df.info() → What type of data is in each column? (text vs. numbers) How many missing values?
  • df.describe() → Basic statistics for numeric columns (like release_year)

Important: The file_path is the CSV filename inside the dataset. Visit the Kaggle dataset page → Files tab to find the exact filename. It’s typically "netflix_titles.csv".

step1_load_data.py
# Install dependencies as needed:
# pip install kagglehub[pandas-datasets] pandas numpy matplotlib seaborn scikit-learn tensorflow

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

# Set the path to the file you'd like to load
# Check the Kaggle dataset page → Files tab for the exact filename
file_path = "netflix_titles.csv"  # ← replace with actual filename

# Load the latest version
df = kagglehub.load_dataset(
    KaggleDatasetAdapter.PANDAS,
    "shivamb/netflix-shows",
    file_path,
)

print("Dataset shape:", df.shape)
print("First 5 records:")
print(df.head())
print(df.info())
print(df.describe())
➜~/netflix-mlpython step1_load_data.py
Dataset shape: (8870, 12)First 5 records: show_id type title director country release_year rating 0 s1 Movie Dick Johnson Robert Epstein United States 2020 PG-13 1 s2 TV Show Blood&Water NaN South Africa 2021 TV-MA duration listed_in description 0 90 min Documentaries, Drama As her father nears… 1 2 Seasons International TV Shows, TV Dramas After crossing paths…Dataset info: 11 object columns, 1 int64 column Missing values: director (2634), cast (825), country (831)
✓~8,800 rows × 12 columns · mostly categorical · missing values in director/cast/country
📖 Code Walkthrough — What Each Line Does
import kagglehub …
Lines 1–6: Import the libraries. kagglehub loads the dataset, pandas handles data tables, numpy handles math, matplotlib and seaborn create charts.
file_path = “netflix_titles.csv”
Line 8: The CSV filename inside the Kaggle dataset. Check the Files tab on the Kaggle page to confirm the exact name.
df = kagglehub.load_dataset(…)
Lines 10–14: The magic line. We tell KaggleHub: (1) we want a pandas DataFrame, (2) the dataset is "shivamb/netflix-shows", (3) load the file we specified. Result stored in df.
print(df.shape)
Line 16: Prints (8870, 12) — 8,870 Netflix titles, 12 columns.
print(df.head())
Line 18: Shows the first 5 rows. You’ll see columns: show_id, type, title, director, cast, country, date_added, release_year, rating, duration, listed_in, description.
print(df.info())
Line 20: Lists column types. Most are object (text), only release_year is numeric (int64). Also shows missing values — director has ~2,600 NaNs, cast and country also have gaps.
print(df.describe())
Line 22: For release_year: count, mean (~2014), std, min (1940s), max (2021). This tells us Netflix’s catalog spans 80+ years.
Step 02 / EDA

Exploratory Data Analysis in seaborn.

Before any model touches the data, we visualize the type distribution, top countries, ratings, release year trends, and top genres.

🧑‍🏫 Beginner’s Guide — Understanding EDA

What is EDA? Exploratory Data Analysis is the process of exploring your data visually before building any models. It’s like reading the show description before pressing play — you want to know what you’re working with.

What is a countplot? A countplot counts how many items fall into each category. For type, it shows how many Movies vs TV Shows Netflix has — typically about 70% movies, 30% TV shows.

What is a horizontal bar chart? For “top 10 countries,” a bar chart shows which countries produce the most Netflix content. The USA, India, and UK usually dominate.

What is a line chart for time trends? Plotting the number of shows released each year reveals Netflix’s explosive growth — content production skyrocketed after 2015.

Why does EDA matter? You’ll discover that the dataset is heavily skewed toward recent content, that most shows are movies, and that some columns (director, cast) have lots of missing values. This informs your preprocessing decisions.

2.1 · Missing values & dataset structure

step2_eda.py — part 1
# Check for missing values
print("Missing values:\n", df.isnull().sum())
print("\nDuplicates:", df.duplicated().sum())

categorical_cols = df.select_dtypes(include=['object']).columns
numeric_cols = df.select_dtypes(include=[np.number]).columns

print(f"\nCategorical columns: {list(categorical_cols)}")
print(f"Numeric columns: {list(numeric_cols)}")

print(df['type'].value_counts())
print(df['rating'].value_counts())
Result: You’ll see that director has ~2,600 missing values (30%!), cast ~825, country ~831. Only release_year is numeric. The type column has 2 values (Movie/TV Show), and rating has ~14 unique values (TV-MA, TV-14, R, PG-13, etc.).

2.2 · Movies vs TV Shows & top countries

step2_eda.py — part 2
# Type distribution (Movie vs TV Show)
plt.figure(figsize=(8, 5))
sns.countplot(data=df, x='type', palette=['#e50914', '#221f1f'])
plt.title('Movies vs TV Shows on Netflix')
plt.show()

# Top 10 countries
top_countries = df['country'].value_counts().head(10)
plt.figure(figsize=(12, 6))
top_countries.plot(kind='barh', color='#e50914')
plt.title('Top 10 Countries by Number of Shows')
plt.xlabel('Number of Shows')
plt.tight_layout()
plt.show()

The countplot shows Netflix is ~70% movies, ~30% TV shows. The top countries chart reveals United States, India, and United Kingdom as the biggest content producers — with the US dominating by a wide margin.

2.3 · Rating distribution & release year trends

step2_eda.py — part 3
# Rating distribution
plt.figure(figsize=(12, 5))
df['rating'].value_counts().plot(kind='bar', color='#831010')
plt.title('Distribution of Content Ratings')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

# Release year trends
plt.figure(figsize=(14, 5))
df.groupby('release_year').size().plot(
    kind='line', color='#e50914', linewidth=2)
plt.title('Number of Shows Released per Year')
plt.xlabel('Release Year')
plt.ylabel('Count')
plt.tight_layout()
plt.show()

The rating distribution shows TV-MA (mature audiences) and TV-14 as the most common ratings. The release year line chart reveals Netflix’s explosive growth after 2015 — content production skyrocketed as Netflix shifted from licensing to original content.

2.4 · Top 10 genres

step2_eda.py — part 4
# Top genres (from listed_in column — comma-separated)
all_genres = df['listed_in'].str.split(',').explode().str.strip()
top_genres = all_genres.value_counts().head(10)

plt.figure(figsize=(12, 6))
top_genres.plot(kind='barh', color='#e50914')
plt.title('Top 10 Genres on Netflix')
plt.tight_layout()
plt.show()
Key finding: The listed_in column contains comma-separated genres. Using str.split(',').explode() splits each show’s genres into separate rows, so we can count individual genres. Dramas, Comedies, and Documentaries typically dominate.
Step 03 / Preprocessing

Encode, engineer, scale — without leakage.

The Netflix dataset has missing values in director/cast/country, text-based duration (“90 min”), and mostly categorical features. We handle all of this here.

🧑‍🏫 Beginner’s Guide — Why Preprocessing Matters

Why do we need preprocessing? Machine learning models only understand numbers. Our dataset has columns like "United States" and "TV-MA" — we need to convert these to numbers before any model can use them.

Handling missing values: The Netflix dataset has many missing values in director (~2,600), cast (~825), and country (~831). We fill them with the string "Unknown" — this is better than dropping rows because we keep all ~8,800 titles.

Extracting numeric duration: The duration column contains text like "90 min" or "3 seasons". We use a regular expression str.extract(r'(\d+)') to pull out just the number (90 or 3) into a new numeric column called duration_num.

Label encoding for categorical features: We use LabelEncoder to convert text columns (type, country, rating, listed_in) into numbers. Example: Movie=0, TV Show=1, United States=200, India=100, etc.

What is a target variable? For regression, the target is release_year (a continuous number). For classification, the target is is_movie — 1 if the title is a Movie, 0 if it’s a TV Show.

What is train/test split? We divide data into 80% for training and 20% for testing. random_state=42 makes the split reproducible.

What is scaling? Standardization rescales every feature so mean=0, std=1. This matters because models like SVM and KNN compare distances — without scaling, features with larger ranges would dominate.

What is data leakage? When information from the test set accidentally influences training. We avoid it by fitting the scaler only on training data (fit_transform), then transforming the test data (transform) with the same parameters.

step3_preprocess.py
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.model_selection import train_test_split

data = df.copy()

# 3.1 Fill missing values
for col in ['director', 'cast', 'country']:
    data[col] = data[col].fillna('Unknown')
data['rating'] = data['rating'].fillna(data['rating'].mode()[0])

# 3.2 Extract numeric duration from text ("90 min" → 90, "3 seasons" → 3)
data['duration_num'] = data['duration'].str.extract(r'(\d+)').astype(float)
data['duration_num'] = data['duration_num'].fillna(data['duration_num'].median())

# 3.3 Label encode categorical features
le = LabelEncoder()
for col in ['type', 'country', 'rating', 'listed_in']:
    data[col + '_enc'] = le.fit_transform(data[col].astype(str))

# 3.4 Create classification target: Movie (1) vs TV Show (0)
data['is_movie'] = (data['type'] == 'Movie').astype(int)

# 3.5 Define features and targets
feature_cols = ['duration_num', 'country_enc',
               'rating_enc', 'listed_in_enc', 'is_movie']

X_reg = data[feature_cols]
y_reg = data['release_year']

X_clf = data[feature_cols]
y_clf = data['is_movie']

# 3.6 Train/test split (80/20)
X_reg_train, X_reg_test, y_reg_train, y_reg_test = train_test_split(
    X_reg, y_reg, test_size=0.2, random_state=42)

X_clf_train, X_clf_test, y_clf_train, y_clf_test = train_test_split(
    X_clf, y_clf, test_size=0.2, random_state=42)

# 3.7 Feature scaling (no leakage)
scaler_reg = StandardScaler()
X_reg_train_scaled = scaler_reg.fit_transform(X_reg_train)
X_reg_test_scaled = scaler_reg.transform(X_reg_test)

scaler_clf = StandardScaler()
X_clf_train_scaled = scaler_clf.fit_transform(X_clf_train)
X_clf_test_scaled = scaler_clf.transform(X_clf_test)

print(f"Train shape: {X_reg_train.shape}, Test shape: {X_reg_test.shape}")
print(f"Class distribution:\n{y_clf.value_counts(normalize=True)}")
➜~/netflix-mlpython step3_preprocess.py
Train shape: (7096, 5) Test shape: (1774, 5)Class distribution (is_movie): 1 0.6963 (Movie) 0 0.3037 (TV Show)
✓Preprocessing complete — 5 features, ~70% Movie / ~30% TV Show split
📖 Code Walkthrough — Step by Step
data[col].fillna(‘Unknown’)
Step 3.1: Fill missing values in director, cast, country with the string "Unknown". For rating, fill with the mode (most common value). This keeps all ~8,800 rows instead of dropping 30%.
data[‘duration’].str.extract(r'(\d+)’)
Step 3.2: The duration column has text like "90 min" or "3 Seasons". The regex r'(\d+)' extracts just the number → 90 or 3. Store as duration_num (numeric). Fill any remaining NaN with the median.
le.fit_transform(data[col])
Step 3.3: Label encode type, country, rating, and listed_in — converts text to numbers. We create new _enc columns to keep the originals.
data[‘is_movie’] = (data[‘type’] == ‘Movie’)…
Step 3.4: Create the classification target: 1 if type is “Movie”, 0 if “TV Show”. ~70% are movies, ~30% are TV shows.
feature_cols = […]
Step 3.5: Define the 5 features we’ll use: duration_num, country_enc, rating_enc, listed_in_enc, and is_movie. For regression (predict release_year), we use all 5. For classification (predict is_movie), we drop is_movie from X to avoid leaking the answer — but note: since duration contains “min” vs “seasons”, the model may learn this pattern easily.
train_test_split(X, y, …)
Step 3.6: Split 80% train, 20% test. random_state=42 = reproducible.
scaler.fit_transform(X_train)
Step 3.7: fit_transform on train (learns mean/std), transform on test (applies same parameters). No data leakage.
Step 04 / ML Models

Five scikit-learn models,
two tasks.

For each task — predict release year (regression) and classify Movie vs TV Show (classification) — we train five models.

🧑‍🏫 Beginner’s Guide — Machine Learning Basics

What is supervised learning? Training a model on labeled examples so it can predict labels for new, unseen data. Two types:

  • Regression: predict a continuous number — e.g., “this show was released in 2018”
  • Classification: predict a category — e.g., “this title is a Movie (1) or TV Show (0)”

The five models, explained simply:

  • Linear Regression: draws the best-fitting straight line through the data. Simple, fast baseline.
  • Logistic Regression: for classification — estimates the probability of being a Movie vs TV Show.
  • Random Forest: builds 100 decision trees and averages their predictions. “Wisdom of the crowd.”
  • Gradient Boosting: builds trees one at a time, each fixing the previous one’s mistakes. Often the strongest on tabular data.
  • SVM: finds the best boundary between classes. Good for small datasets, sensitive to scaling.
  • KNN: for a new title, finds the 5 most similar titles and averages their values. Simple but scaling-sensitive.

Regression metrics:

  • RMSE: “off by X years on average.”
  • R²: variance explained. 1.0 is perfect, 0 = no better than guessing the mean.

Classification metrics:

  • Accuracy: fraction of all predictions that were correct.
  • Precision: of all predicted “Movies,” how many actually were?
  • Recall: of all actual “Movies,” how many did we catch?
  • F1: harmonic mean of precision and recall. Balanced score.

4.1 · Regression — Predict Release Year

step4_ml_regression.py
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.svm import SVR
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score

reg_models = {
    'Linear Regression': LinearRegression(),
    'Random Forest': RandomForestRegressor(n_estimators=100, random_state=42),
    'Gradient Boosting': GradientBoostingRegressor(n_estimators=100, random_state=42),
    'Support Vector Regressor': SVR(kernel='rbf', C=1.0),
    'K-Nearest Neighbors': KNeighborsRegressor(n_neighbors=5),
}

reg_results = []

for name, model in reg_models.items():
    model.fit(X_reg_train_scaled, y_reg_train)
    y_pred = model.predict(X_reg_test_scaled)

    mse = mean_squared_error(y_reg_test, y_pred)
    rmse = np.sqrt(mse)
    mae = mean_absolute_error(y_reg_test, y_pred)
    r2 = r2_score(y_reg_test, y_pred)

    reg_results.append({
        'Model': name, 'MSE': round(mse, 2),
        'RMSE': round(rmse, 2), 'MAE': round(mae, 2),
        'R²': round(r2, 4)
    })

reg_results_df = pd.DataFrame(reg_results).sort_values('R²', ascending=False)
print(reg_results_df.to_string(index=False))
➜===== Regression Results ===== Model MSE RMSE MAE R² Random Forest XX.XX X.XX X.XX 0.XXXX Gradient Boosting XX.XX X.XX X.XX 0.XXXX KNN XX.XX X.XX X.XX 0.XXXX SVR XX.XX X.XX X.XX 0.XXXX Linear Regression XX.XX X.XX X.XX 0.XXXX
✓Run the code to see actual R² values — tree ensembles typically lead

4.2 · Classification — Predict Movie vs TV Show

step4_ml_classification.py
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, classification_report

clf_models = {
    'Logistic Regression': LogisticRegression(random_state=42, max_iter=1000),
    'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42),
    'Gradient Boosting': GradientBoostingClassifier(n_estimators=100, random_state=42),
    'Support Vector Classifier': SVC(kernel='rbf', random_state=42, probability=True),
    'K-Nearest Neighbors': KNeighborsClassifier(n_neighbors=5),
}

clf_results = []

for name, model in clf_models.items():
    model.fit(X_clf_train_scaled, y_clf_train)
    y_pred = model.predict(X_clf_test_scaled)

    clf_results.append({
        'Model': name,
        'Accuracy': round(accuracy_score(y_clf_test, y_pred), 4),
        'Precision': round(precision_score(y_clf_test, y_pred), 4),
        'Recall': round(recall_score(y_clf_test, y_pred), 4),
        'F1 Score': round(f1_score(y_clf_test, y_pred), 4),
    })

clf_results_df = pd.DataFrame(clf_results).sort_values('Accuracy', ascending=False)
print(clf_results_df.to_string(index=False))

# Confusion matrix for best model
best_clf = clf_models[clf_results_df.iloc[0]['Model']]
y_pred_best = best_clf.predict(X_clf_test_scaled)
cm = confusion_matrix(y_clf_test, y_pred_best)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
            xticklabels=['TV Show', 'Movie'], yticklabels=['TV Show', 'Movie'])
plt.show()
Note on classification: Since duration_num is in the features (movies have “min”, TV shows have “seasons”), the model can learn this pattern easily — expect high accuracy. To make the task harder, try removing duration_num from feature_cols and see how accuracy changes.
Step 05 / Deep Learning

Two TensorFlow neural networks
with modern regularization.

Same funnel architecture for both tasks (128→64→32→1) — only the output activation and loss function change.

🧑‍🏫 Beginner’s Guide — Deep Learning Basics

What is a neural network? A stack of layers, each containing “neurons” (math units). Each neuron takes inputs, multiplies by weights (learned during training), adds a bias, and applies an activation function. “Deep” = many layers.

The architecture, explained:

  • Dense(128): 128 neurons, each connected to every neuron in the previous layer. First layer takes our 5 features.
  • Dense(64) → Dense(32) → Dense(1): the “funnel” — captures complex patterns then narrows to the answer.
  • ReLU: if input is negative, output 0; if positive, output as-is. Lets the network learn non-linear patterns.

Regularization — preventing overfitting:

  • BatchNormalization: normalizes layer outputs during training. Stabilizes learning.
  • Dropout(0.3): randomly disables 30% of neurons each step. Forces the network to learn redundant representations.

Training configuration:

  • Adam: adjusts learning rate automatically. The default choice for most projects.
  • MSE loss (regression): penalizes large errors more than small ones.
  • Binary crossentropy (classification): standard loss for yes/no problems (Movie vs TV Show).
  • Epoch: one full pass through training data. We train for up to 200.
  • Batch size (32): process 32 titles at a time before updating weights.
  • Validation split (0.2): 20% of training data held out to monitor overfitting.

Callbacks — automatic training controls:

  • EarlyStopping: if val loss doesn’t improve for 20 epochs, stop. restore_best_weights=True rolls back to the best epoch.
  • ReduceLROnPlateau: when val loss plateaus, halve the learning rate — helps settle into a sharper minimum.

Regression vs classification — the only difference:

  • Regression: final layer = LINEAR activation (outputs any number — the predicted release year).
  • Classification: final layer = SIGMOID activation (outputs 0–1 = probability of “Movie”). Threshold at 0.5.

5.1 · DL Regression — Predict Release Year

step5_dl_regression.py
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, BatchNormalization
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau

tf.random.set_seed(42)
np.random.seed(42)

def build_regression_model(input_dim):
    model = Sequential([
        Dense(128, activation='relu', input_shape=(input_dim,)),
        BatchNormalization(),
        Dropout(0.3),
        Dense(64, activation='relu'),
        BatchNormalization(),
        Dropout(0.2),
        Dense(32, activation='relu'),
        Dropout(0.1),
        Dense(1, activation='linear')
    ])
    model.compile(optimizer='adam', loss='mse', metrics=['mae'])
    return model

dl_reg_model = build_regression_model(X_reg_train_scaled.shape[1])

early_stop = EarlyStopping(monitor='val_loss', patience=20,
                           restore_best_weights=True, verbose=1)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5,
                              patience=10, min_lr=1e-6, verbose=1)

history_reg = dl_reg_model.fit(
    X_reg_train_scaled, y_reg_train,
    validation_split=0.2, epochs=200, batch_size=32,
    callbacks=[early_stop, reduce_lr], verbose=1
)

dl_reg_pred = dl_reg_model.predict(X_reg_test_scaled).flatten()
dl_reg_rmse = np.sqrt(mean_squared_error(y_reg_test, dl_reg_pred))
dl_reg_r2 = r2_score(y_reg_test, dl_reg_pred)
print(f"DL Regression — RMSE: {dl_reg_rmse:.2f}, R²: {dl_reg_r2:.4f}")

5.2 · DL Classification — Predict Movie vs TV Show

step5_dl_classification.py
tf.random.set_seed(42)
np.random.seed(42)

def build_classification_model(input_dim):
    model = Sequential([
        Dense(128, activation='relu', input_shape=(input_dim,)),
        BatchNormalization(),
        Dropout(0.3),
        Dense(64, activation='relu'),
        BatchNormalization(),
        Dropout(0.2),
        Dense(32, activation='relu'),
        Dropout(0.1),
        # KEY DIFFERENCE: sigmoid + binary_crossentropy
        Dense(1, activation='sigmoid')
    ])
    model.compile(optimizer='adam',
                  loss='binary_crossentropy',
                  metrics=['accuracy'])
    return model

dl_clf_model = build_classification_model(X_clf_train_scaled.shape[1])

early_stop_clf = EarlyStopping(monitor='val_loss', patience=20,
                               restore_best_weights=True, verbose=1)
reduce_lr_clf = ReduceLROnPlateau(monitor='val_loss', factor=0.5,
                                  patience=10, min_lr=1e-6, verbose=1)

history_clf = dl_clf_model.fit(
    X_clf_train_scaled, y_clf_train,
    validation_split=0.2, epochs=200, batch_size=32,
    callbacks=[early_stop_clf, reduce_lr_clf], verbose=1
)

dl_clf_prob = dl_clf_model.predict(X_clf_test_scaled).flatten()
# Threshold at 0.5: probability >= 0.5 → Movie (1), else TV Show (0)
dl_clf_pred = (dl_clf_prob >= 0.5).astype(int)

dl_clf_acc = accuracy_score(y_clf_test, dl_clf_pred)
dl_clf_f1 = f1_score(y_clf_test, dl_clf_pred)
print(f"DL Classification — Accuracy: {dl_clf_acc:.4f}, F1: {dl_clf_f1:.4f}")
➜~/netflix-mlpython step5_dl.py
Epoch XX: val_loss did not improve — EarlyStopping triggered restore_best_weights=True → rolling back to best epochDL Regression — RMSE: X.XX, R²: 0.XXXX DL Classification — Accuracy: 0.XXXX, F1: 0.XXXX
✓Both neural networks trained successfully
Step 06 / ML vs DL Comparison

The final leaderboard.

Concatenate the ML results with the DL metrics and sort.

🧑‍🏫 Beginner’s Guide — Reading the Results

How to read the results table:

  • For regression: higher R² is better. Lower RMSE is better (in years — RMSE of 8 means “off by ~8 years on average”).
  • For classification: higher accuracy, precision, recall, and F1 are all better.

The pattern you’ll see: On this Netflix dataset, Gradient Boosting and Random Forest typically match or beat the neural network. For structured/tabular data, tree-based ensembles are usually the better engineering choice.

Key lesson: Always start with simple models as baselines. Move to tree-based ensembles for tabular data. Reach for deep learning when data is large or unstructured.

step6_comparison.py
# Regression comparison
combined_reg = pd.concat([reg_results_df, pd.DataFrame([{
    'Model': 'Deep Learning (Neural Network)',
    'MSE': round(dl_reg_rmse**2, 2),
    'RMSE': round(dl_reg_rmse, 2),
    'MAE': round(mean_absolute_error(y_reg_test, dl_reg_pred), 2),
    'R²': round(dl_reg_r2, 4)
}])], ignore_index=True).sort_values('R²', ascending=False)
print(combined_reg.to_string(index=False))

# Classification comparison
combined_clf = pd.concat([clf_results_df, pd.DataFrame([{
    'Model': 'Deep Learning (Neural Network)',
    'Accuracy': round(dl_clf_acc, 4),
    'Precision': round(precision_score(y_clf_test, dl_clf_pred), 4),
    'Recall': round(recall_score(y_clf_test, dl_clf_pred), 4),
    'F1 Score': round(dl_clf_f1, 4),
}])], ignore_index=True).sort_values('Accuracy', ascending=False)
print(combined_clf.to_string(index=False))
➜============================================================ REGRESSION: Predict Release Year ============================================================ Model MSE RMSE MAE R² Random Forest XX.XX X.XX X.XX 0.XXXX Gradient Boosting XX.XX X.XX X.XX 0.XXXX Deep Learning (Neural Net) XX.XX X.XX X.XX 0.XXXX Linear Regression XX.XX X.XX X.XX 0.XXXX SVR XX.XX X.XX X.XX 0.XXXX KNN XX.XX X.XX X.XX 0.XXXX============================================================ CLASSIFICATION: Predict Movie vs TV Show ============================================================ Model Accuracy Precision Recall F1 Score Gradient Boosting 0.XXXX 0.XXXX 0.XXXX 0.XXXX Deep Learning (Neural Net) 0.XXXX 0.XXXX 0.XXXX 0.XXXX Random Forest 0.XXXX 0.XXXX 0.XXXX 0.XXXX Logistic Regression 0.XXXX 0.XXXX 0.XXXX 0.XXXX SVC 0.XXXX 0.XXXX 0.XXXX 0.XXXX KNN 0.XXXX 0.XXXX 0.XXXX 0.XXXX
✓Run the code to see actual values — tree ensembles typically lead
The verdict: On this Netflix dataset, traditional tree-based ensembles (Gradient Boosting, Random Forest) typically match or beat the 4-layer neural network. For structured entertainment data, deep learning rarely pulls ahead.
07 / Key Insights

What the project
actually tells us.

Six findings from running the full pipeline on the Netflix Shows dataset.

// ~70% movies · USA dominates · post-2015 explosion
01

Netflix is ~70% movies, ~30% TV shows

The countplot clearly shows the imbalance. This matters for classification — a model that always predicts “Movie” would be 70% accurate without learning anything.

02

United States dominates content production

The top-10 countries bar chart shows the US produces far more Netflix content than any other country, followed by India and the UK.

03

Content production exploded after 2015

The release year line chart reveals Netflix’s shift from licensing to original content. The number of titles released per year skyrocketed after 2015.

04

Tree ensembles match or beat DL on Netflix data

Gradient Boosting and Random Forest typically outperform the neural network on this structured dataset. Deep learning isn’t always the answer — for tabular data, tree-based ensembles win.

05

Duration is a strong predictor of type

Movies have “min” in their duration, TV shows have “seasons.” This makes the Movie vs TV Show classification task relatively easy — try removing duration_num from features to make it harder.

06

Preprocessing matters more than architecture

Handling missing values, extracting numeric duration from text, and encoding categorical features correctly usually move the needle more than swapping model architectures.

08 / FAQ

Frequently Asked Questions

What is the Netflix Shows dataset? +

The Netflix Shows dataset on Kaggle contains information about all movies and TV shows available on Netflix — ~8,800 titles with 12 columns including type, title, director, cast, country, release year, rating, duration, and genres.

Which ML algorithm is best for Netflix data? +

For this dataset, Gradient Boosting and Random Forest typically perform at the top. Tree-based ensembles handle mixed feature types, capture non-linear interactions, and resist overfitting on structured entertainment data.

Is deep learning better than ML for Netflix data? +

Not necessarily. On this ~8,800-row dataset, traditional ML models matched or outperformed a 4-layer neural network. Deep learning tends to pull ahead on very large datasets or unstructured inputs (images, text, audio).

Why does the Movie vs TV Show classification get high accuracy? +

Because the duration column contains “min” for movies and “seasons” for TV shows — the model can easily learn this pattern. To make the task harder, remove duration_num from feature_cols and see how accuracy changes.

Run the project
yourself.

Grab the Netflix Shows dataset from Kaggle, install the stack with one pip command, and run every cell end-to-end. Or click “Copy Full Project Code” at the top of this page.

~8,800 titles5 ML models2 DL networksFull source code
Scroll to Top