Retail Sales Prediction with GeoPandas

Build regression models that predict retail order costs using geospatial features.

Introduction

30 Second Summary

Ever wonder why two stores selling the same products can have wildly different sales numbers? Where a store sits relative to its customers and its regional market shapes how much revenue it generates.

In this project, you will build a retail sales prediction tool in Python that analyzes 40,000+ orders from Brazilian marketplaces. You will engineer geographic features, build regression models, and prove that location data measurably improves prediction accuracy.

What You'll Build

Picture running your script and watching a map of Brazil light up with color-coded store locations, then scrolling to a results table where your geo-enhanced model visibly outperforms the baseline.

By the end of this project, you'll have:

  • A prediction tool that takes store and customer attributes and returns a predicted order cost, with three models compared side-by-side to quantify the accuracy gain from adding geographic features.
  • A geographic scatter plot of store locations across Brazil, colored by average sales performance, built with GeoPandas.
  • A feature importance chart revealing which variables drive predictions, from store area and income to customer-store distance and region.
  • Secret Mission: Build a geographic error heatmap that reveals which regions of Brazil the model struggles with most.

Are there any prerequisites?

This project assumes intermediate-to-advanced Python and data science knowledge. You should be comfortable with pandas, numpy, and scikit-learn basics before starting.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Your Environment and Download the Dataset

You need the right Python libraries and the retail dataset before you can start exploring sales patterns. Without an isolated environment, library version conflicts can silently break your analysis.

In this step, you will create a dedicated project folder, install six libraries for data analysis and geospatial modeling, and download the Brazilian marketplace dataset from Kaggle.

In this step, get ready to:
  • Create a project directory and virtual environment.
  • Install the required Python libraries.
  • Download the CAT Datathon 1.0 dataset and create your Python file.
Create your project directory and virtual environment

A virtual environment isolates your project's library versions from other Python projects on your machine. This prevents version conflicts between projects.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir retail-sales-prediction
cd retail-sales-prediction
  • Create a virtual environment inside the project folder by running:
python -m venv venv
  • Confirm the virtual environment was created by running:
ls

You should see a venv folder in the output.

  • Activate the virtual environment by running the command for your operating system:

macOS

source venv/bin/activate

Windows

.\venv\Scripts\Activate.ps1

Seeing a script execution error?

PowerShell may block scripts by default. Run this command first to allow local scripts, then try the activation command again:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Your terminal prompt now starts with (venv). This confirms you are working inside the virtual environment.

Don't see (venv) in your prompt?

Make sure you ran the activation command for the correct operating system. macOS uses source venv/bin/activate and Windows uses .\venv\Scripts\Activate.ps1.

Check that you are inside the retail-sales-prediction folder by running ls and confirming you see the venv folder.

help me activate my Python virtual environment

Install the required libraries
  • Install all six libraries by running this command:
pip install pandas==2.2.3 numpy==2.1.3 geopandas==1.1.4 scikit-learn==1.9.0 matplotlib==3.9.4 seaborn==0.13.2

What do these libraries do?

  • pandas loads and manipulates tabular data (like your retail CSV).
  • NumPy handles fast numerical calculations, including the Haversine distance formula you will use later.
  • GeoPandas extends pandas with geographic data types, letting you plot store locations on a map.
  • scikit-learn provides the regression models and evaluation metrics you will train and compare.
  • matplotlib and seaborn generate the charts and heatmaps you will use to visualize patterns.

The install may take a minute or two while pip downloads and builds the packages. Once it finishes, what do you think will happen if you try to import all six libraries in one go?

  • Verify all libraries installed correctly by running:
python -c "import pandas; import numpy; import geopandas; import sklearn; import matplotlib; print('All libraries imported successfully')"

You should see All libraries imported successfully printed in your terminal.

Seeing a ModuleNotFoundError?

Make sure your virtual environment is active. Your prompt should start with (venv). If it does not, reactivate it with the command for your operating system, then rerun the pip install command.

If the error names a specific library, try installing just that library again (for example, pip install geopandas==1.1.4).

help me fix a ModuleNotFoundError when importing my data science libraries

Download the dataset and create your Python file

The CAT Datathon 1.0 dataset contains over 40,000 orders from Brazilian marketplaces. Each record includes store attributes, customer details, and geographic coordinates for both sellers and customers.

  • Open the dataset page on Kaggle: CAT Datathon 1.0 Dataset.
  • Click Download to download the dataset.
  • Sign in with a free Kaggle account if prompted.
  • If the download is a zip file, unzip it to get the CSV file inside.
  • Move the CSV file into your retail-sales-prediction folder on your Desktop.
  • Rename the file to cat_datathon_dataset.csv if it has a different name.

Do I need a Kaggle account?

Kaggle is free to join. If you do not have an account, click Register on the Kaggle page and sign up with Google or email. The download button appears after you sign in.

  • Create an empty Python file for your project code by running the command for your operating system:

macOS

touch sales_prediction.py

Windows

New-Item sales_prediction.py -ItemType File

Prefer Jupyter notebooks?

You can use a Jupyter notebook (sales_prediction.ipynb) instead of a .py file. The code in the rest of this project works in either format.

  • Confirm all your project files are in place by running:
ls

You should see cat_datathon_dataset.csv, sales_prediction.py, and venv listed in the output.

Missing the CSV file?

Make sure you moved the downloaded file into the retail-sales-prediction folder, not a subfolder inside it. If the download came as a zip, unzip it first.

The CSV file must be named exactly cat_datathon_dataset.csv for the code in later steps to find it.

help me locate and rename my downloaded CSV file

Your workspace is ready with all six libraries installed and a real-world dataset waiting to be explored. Next up, you will load that dataset, clean it, and get your first look at the sales patterns hiding inside.

Load, Clean, and Explore the Sales Data

Your environment is set up and the dataset is sitting in your project directory. Now it's time to crack open the data.

Before building any prediction model, you need to understand the raw material. How many orders are in the dataset? Are there missing values that could break your geographic analysis later? This step answers those questions and produces your first visual output.

In this step, get ready to:
  • Load the dataset and clean rows with missing geographic coordinates.
  • Visualize the target variable distribution and column correlations.
  • Explore the categorical and geographic spread of the data.
Import libraries and load the dataset

Your sales_prediction.py file is currently empty. You'll start by adding all the imports the project needs, then load the CSV and check its quality.

  • Open sales_prediction.py in your editor and add the following imports:
import pandas as pd
import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import r2_score, mean_absolute_error

What do these imports do?

  • pandas and numpy handle data loading, cleaning, and numeric operations.
  • geopandas extends pandas with geographic data support. You'll use it in a later step to plot store locations on a map.
  • matplotlib and seaborn produce charts and heatmaps for visual exploration.
  • The sklearn imports bring in regression models and evaluation metrics. You'll use these starting in Step 3.
  • Save sales_prediction.py.
  • Load and inspect the dataset by adding this code below your imports:
# --------------------------------------------------
# STEP 2: Load, Clean, and Explore
# --------------------------------------------------
df = pd.read_csv("cat_datathon_dataset.csv")
print("Shape:", df.shape)
print("\nData types:\n", df.dtypes)
print("\nMissing values:\n", df.isnull().sum())

geo_cols = ["Seller Latitude", "Seller Longitude", "Customer Latitude", "Customer Longitude"]
df = df.dropna(subset=geo_cols)
print("\nCleaned shape:", df.shape)

What does this code do?

  • pd.read_csv() loads the CSV into a DataFrame called df. The three print() calls show the row/column count, each column's data type, and how many values are missing per column.
  • geo_cols lists the four latitude/longitude columns that the geographic analysis depends on. dropna(subset=geo_cols) removes any row missing one of those coordinates, because you can't calculate distances without them.
  • Save sales_prediction.py.
  • Run the script in your terminal by running:
python sales_prediction.py

You should see the dataset's shape (40,000+ rows), the data types for each column, a missing-values count, and a cleaned shape after dropping incomplete rows.

Seeing an ImportError or ModuleNotFoundError?

  • Make sure your virtual environment is active. You should see (venv) at the start of your terminal prompt.
  • If a specific library is missing, re-run the pip install command from Step 1 with the missing package name.
  • If you see a FileNotFoundError, confirm that cat_datathon_dataset.csv is in the same folder as sales_prediction.py.

Help me debug my import or file loading error.

Visualize the cost distribution and correlations

The Cost column is what your model will predict. Seeing its distribution tells you whether most orders cluster around a typical value or spread widely. A correlation heatmap reveals which numeric columns move together.

  • Generate summary statistics and a two-panel figure by adding this code below the cleaned-shape print statement:
print("\nSummary statistics:\n", df.describe())

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

axes[0].hist(df["Cost"], bins=50, edgecolor="black")
axes[0].set_title("Distribution of Cost (Target Variable)")
axes[0].set_xlabel("Cost")
axes[0].set_ylabel("Frequency")

numeric_cols = df.select_dtypes(include=[np.number]).columns
corr_matrix = df[numeric_cols].corr()
sns.heatmap(corr_matrix, ax=axes[1], cmap="coolwarm", center=0,
            xticklabels=True, yticklabels=True)
axes[1].set_title("Correlation Heatmap")
plt.tight_layout()
plt.savefig("eda_plots.png", dpi=150)
plt.show()

What does this code do?

  • df.describe() prints summary statistics (mean, median, min, max) for every numeric column.
  • plt.subplots(1, 2) creates a figure with two side-by-side panels. The left panel is a histogram of Cost showing how order costs are distributed.
  • The right panel is a seaborn correlation heatmap. Red cells mean two columns increase together. Blue cells mean one goes up as the other goes down.
  • plt.savefig() saves the figure as eda_plots.png in your project directory so you can reference it later.
  • Save sales_prediction.py.

Before you run the script, what do you think the distribution of order costs will look like? Will most orders cluster around a low value, or will costs be evenly spread?

  • Run the script in your terminal by running:
python sales_prediction.py

A matplotlib window opens with two panels. The left panel shows the histogram of Cost. The right panel shows the correlation heatmap. Close the figure window to let the script finish.

No figure window appearing?

  • If the script hangs without showing a window, your matplotlib backend may not support interactive display. Check that eda_plots.png was saved in your project folder and open it manually.
  • If you're using a Jupyter notebook, replace plt.show() with %matplotlib inline at the top of the notebook. The figure displays inline automatically.

Help me fix my matplotlib figure display.

Explore the categorical columns

The dataset covers orders from multiple Brazilian states and different store types. Printing the unique values tells you how geographically spread the data is and what categorical features you have to work with.

  • Print the unique values for Seller State and Store Kind by adding this code at the bottom of your file:
print("\nUnique Seller States:", df["Seller State"].nunique(), "-", df["Seller State"].unique())
print("Unique Store Kinds:", df["Store Kind"].nunique(), "-", df["Store Kind"].unique())
  • Save sales_prediction.py.

Before you run the script, how many unique Brazilian states do you think the dataset covers?

  • Run the script in your terminal by running:
python sales_prediction.py

At the bottom of the terminal output, you should see the count of unique seller states and store kinds, followed by the list of each value. The figure window also opens again. Close it to let the script finish.

Why does the figure open every time?

plt.show() runs each time you execute the script, so the figure pops up on every run. This is normal. Close the window and the rest of the terminal output (including the categorical values) prints immediately after.

✔️ Awesome, I've got everything!

Great. Double-check you've saved sales_prediction.py and that eda_plots.png appears in your project folder.

ⓧ I'd like to double check the full code

Scroll up and confirm your file has each of these sections in order:

  • The 9-line import block at the top (pandas, numpy, geopandas, matplotlib, seaborn, sklearn).
  • The load-and-clean block: pd.read_csv(), three print calls, dropna(), and the cleaned-shape print.
  • The visualization block: df.describe(), the 2-panel figure (histogram + heatmap), savefig(), and show().
  • The two print statements for Seller State and Store Kind unique values at the bottom.

You've explored the dataset and produced your first visualizations. You know the shape of the data, where values are missing, and how the target variable is distributed. Next up, you'll build a baseline regression model to predict order costs using only store attributes.

Build a Baseline Regression Model

Your data is cleaned and explored. You know the shape of your dataset, the distribution of the Cost target variable, and how columns correlate with each other.

Now it is time to see how well you can predict order costs. Your first model uses only store-level attributes like area, sales figures, and product weights. No customer location data, no geography. This gives you a clean performance baseline to measure against as you add more features.

In this step, get ready to:
  • Select store attribute features and encode categorical data for modeling.
  • Train a Linear Regression model and measure its accuracy with R2 and MAE.
  • Inspect model coefficients to see which store attributes the model relies on most.
Select baseline features and prepare the data

A regression model needs a defined set of input features and a target variable to learn from. You will choose nine numeric store attributes as inputs and the Cost column as the value the model tries to predict.

  • In sales_prediction.py, scroll to the bottom of the file and add the following code below the last print statement:
# --------------------------------------------------
# STEP 3: Baseline Regression (No Geographic Features)
# --------------------------------------------------
baseline_features = [
    "Store Cost", "Store Sales", "Store Area",
    "Grocery Area", "Frozen Area", "Meat Area",
    "Yearly Income", "Gross Weight", "Net Weight",
]

df_model = df.dropna(subset=baseline_features + ["Cost"])
store_kind_dummies = pd.get_dummies(df_model["Store Kind"], prefix="StoreKind", drop_first=True)
X_baseline = pd.concat([df_model[baseline_features], store_kind_dummies], axis=1)
y = df_model["Cost"]

What does this code do?

  • baseline_features lists nine numeric columns that describe the store itself. These are all non-geographic attributes.
  • df_model drops any rows with missing values in the baseline features or the Cost target, so the model trains on clean data only.
  • get_dummies() converts the categorical Store Kind column into binary columns the model can work with. drop_first=True prevents multicollinearity by dropping one dummy category.
  • X_baseline is the feature matrix (inputs) and y is the target vector (the order cost the model predicts).
  • Save sales_prediction.py.
Train the baseline model and evaluate its accuracy

With the features and target ready, you can split the data into training and test sets, train a LinearRegression model, and measure how well it predicts unseen orders.

  • Add the following code directly below the line y = df_model["Cost"]:
X_train_b, X_test_b, y_train, y_test = train_test_split(
    X_baseline, y, test_size=0.2, random_state=42
)

lr_baseline = LinearRegression()
lr_baseline.fit(X_train_b, y_train)
y_pred_baseline = lr_baseline.predict(X_test_b)

r2_baseline = r2_score(y_test, y_pred_baseline)
mae_baseline = mean_absolute_error(y_test, y_pred_baseline)
print(f"\n--- Baseline Linear Regression ---")
print(f"R2 Score:  {r2_baseline:.4f}")
print(f"MAE:       {mae_baseline:.4f}")

What does this code do?

  • train_test_split holds back 20% of the data as a test set so you can evaluate the model on orders it has never seen.
  • random_state=42 locks the split so you get the same train/test rows every time you run the script. This makes your results reproducible.
  • R2 measures how much of the variance in order cost the model explains. A score of 1.0 means a perfect prediction. MAE (Mean Absolute Error) measures the average distance between predicted and actual costs in the same units as the target.

Before you run the script, pause for a moment. Do you think this model, using only store attributes, will predict order costs accurately? What R2 score do you expect?

  • Save sales_prediction.py.
  • Run the script in your terminal by running:
python sales_prediction.py

After the Step 2 output, you should see a new section titled --- Baseline Linear Regression --- followed by the R2 and MAE scores. The R2 is noticeably below 1.0.

Why is the R2 so low?

This result is expected. The model only knows about the store itself. It has no idea where the customer is, how far they are from the store, or which region of Brazil the order comes from.

Those latitude and longitude columns are sitting in the dataset unused. Your R2 and MAE scores are now your "before" numbers. Write them down.

Seeing a KeyError or missing column error?

  • Make sure every column name in baseline_features matches the dataset exactly, including capitalisation and spacing (e.g. Store Cost not store_cost).
  • Confirm that the Step 2 code above your new code still runs without errors. The df variable must be defined before Step 3 uses it.
  • Check that cat_datathon_dataset.csv is in the same folder as sales_prediction.py.

Help me debug a KeyError when building my baseline regression model in pandas

Inspect the model coefficients

A linear regression model assigns a coefficient to each feature. The larger the absolute coefficient, the stronger that feature's influence on the predicted cost.

  • Add the following code directly below the print(f"MAE: {mae_baseline:.4f}") line:
coef_df = pd.DataFrame({
    "Feature": X_baseline.columns,
    "Coefficient": lr_baseline.coef_
}).sort_values("Coefficient", key=abs, ascending=False)
print("\nTop coefficients:\n", coef_df.head(10).to_string(index=False))

What does this code do?

  • coef_df pairs each feature name with the coefficient the model learned for it.
  • Sorting by absolute value puts the most influential features at the top, regardless of whether they push the cost up or down.
  • head(10) prints only the top 10 features so the output stays readable.
  • Save sales_prediction.py and run the script again:
python sales_prediction.py

Below the R2 and MAE output, you should now see a table of the top 10 features ranked by coefficient magnitude. These are the store attributes the baseline model leans on most.

Not seeing the coefficients table?

  • Make sure the new code sits after the print(f"MAE: ... line, not inside a different block or indented under an if statement.
  • Check for a SyntaxError earlier in the output. If the script fails before reaching this code, fix the earlier error first.

Help me debug why my coefficient DataFrame is not printing

✔️ Awesome, I've got everything!

Great. Double check you have saved sales_prediction.py.

ⓧ I'd like to double check the full code

The full file is now too long to show in one block. Scroll up through your code and confirm these sections appear in order:

  • The imports at the top (pandas, numpy, geopandas, matplotlib, seaborn, and sklearn).
  • The Step 2 section: loading the CSV, cleaning geographic nulls, generating EDA plots, and printing unique states and store kinds.
  • The Step 3 header comment: # STEP 3: Baseline Regression (No Geographic Features).
  • The baseline_features list, df_model creation, store_kind_dummies, X_baseline, and y.
  • The train/test split, LinearRegression training, and R2/MAE prints.
  • The coef_df DataFrame and the top 10 coefficients print at the very bottom.

You now have concrete baseline numbers: an R2 score and MAE that prove store attributes alone leave a lot of prediction accuracy on the table. Next up, you will use GeoPandas to engineer geographic features from those unused latitude and longitude columns and see if location data can close the gap.

Engineer Geographic Features with GeoPandas

Your baseline model produced a mediocre R2 score. It knows about store size, cost, and income, but it has no idea where anything is located. That is a significant blind spot for a retail prediction model.

Geography shapes retail performance. A store in São Paulo serving customers 2,000 km away faces different dynamics than one serving local buyers. In this step, you will use GeoPandas to turn raw latitude and longitude values into meaningful features that capture these spatial patterns.

In this step, get ready to:
  • Plot store locations across Brazil on a geographic scatter map.
  • Calculate the real distance in kilometers between each customer and their store.
  • Encode seller state as a numeric feature for modeling.
Create a GeoDataFrame and map store locations

A regular pandas DataFrame stores your latitude and longitude as plain numbers. A GeoDataFrame understands that those numbers represent points on the Earth, which unlocks geographic plotting and spatial operations.

  • Add the following code at the bottom of sales_prediction.py, below your Step 3 code:
# --------------------------------------------------
# STEP 4: Geographic Feature Engineering with GeoPandas
# --------------------------------------------------
gdf = gpd.GeoDataFrame(
    df_model,
    geometry=gpd.points_from_xy(
        df_model["Seller Longitude"],
        df_model["Seller Latitude"],
        crs="EPSG:4326",
    ),
)

fig, ax = plt.subplots(figsize=(10, 8))
gdf.plot(
    ax=ax,
    column="Store Sales",
    cmap="YlOrRd",
    legend=True,
    markersize=5,
    alpha=0.6,
)
ax.set_title("Store Locations by Sales Performance")
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
plt.savefig("store_map.png", dpi=150)
plt.show()

What does this code do?

  • gpd.GeoDataFrame wraps your existing df_model with geometric point data so GeoPandas can treat each row as a location on a map.
  • gpd.points_from_xy converts the longitude and latitude columns into geometric point objects.
  • crs="EPSG:4326" sets the coordinate reference system to standard GPS coordinates, the same system your lat/lon values are recorded in.
  • The .plot() method renders each store as a dot, colored by Store Sales using the yellow-to-red YlOrRd colormap. Higher-selling stores appear darker red.
  • Save sales_prediction.py.
  • Run the script by running:
python sales_prediction.py

After the Step 2 and Step 3 output you have already seen, a new matplotlib window appears showing a scatter plot of store locations across Brazil. Points are colored from yellow (low sales) to red (high sales), revealing geographic clusters of high-performing stores.

Don't see the map?

  • Make sure you saved sales_prediction.py before running the script.
  • If the plot window opens behind your editor, check your taskbar for a Python or matplotlib icon.
  • Close the first two plot windows (EDA plots from Step 2) before the store map window appears. The script displays them in order.

Still stuck? Help me debug why my GeoPandas scatter plot is not appearing.

Calculate customer-to-store distance with the Haversine formula

Your dataset has separate latitude and longitude columns for both the store and the customer. A model cannot learn from raw coordinates on their own. The distance between a customer and their store is a far more useful signal.

  • Add the following code at the bottom of sales_prediction.py, below the plt.show() line you just added:
def haversine_km(lat1, lon1, lat2, lon2):
    """Calculate the Haversine distance in kilometers."""
    r = 6371
    lat1, lon1, lat2, lon2 = map(np.radians, [lat1, lon1, lat2, lon2])
    dlat = lat2 - lat1
    dlon = lon2 - lon1
    a = np.sin(dlat / 2) ** 2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2) ** 2
    c = 2 * np.arcsin(np.sqrt(a))
    return r * c


df_model = df_model.copy()
df_model["customer_store_distance_km"] = haversine_km(
    df_model["Customer Latitude"].values,
    df_model["Customer Longitude"].values,
    df_model["Seller Latitude"].values,
    df_model["Seller Longitude"].values,
)
print("\nDistance sample:")
print(
    df_model[["Seller City", "Customer City", "customer_store_distance_km"]]
    .head()
    .to_string(index=False)
)

What does this code do?

  • The Haversine formula calculates the great-circle distance between two points on a sphere. It accounts for the curvature of the Earth, giving you a realistic distance in kilometers.
  • r = 6371 is the Earth's mean radius in kilometers.
  • np.radians converts degrees to radians because trigonometric functions in numpy expect radian inputs.
  • df_model.copy() creates an independent copy of the DataFrame to avoid pandas SettingWithCopyWarning when adding the new column.

Why Haversine instead of GeoPandas .distance()?

GeoPandas has a built-in .distance() method, but when your coordinate reference system is EPSG:4326 (GPS coordinates), it returns distance in degrees, not kilometers. Degrees do not correspond to a fixed real-world distance.

The Haversine formula gives you a meaningful kilometer value without needing to reproject the data into a different coordinate system.

  • Save sales_prediction.py.
  • Before you run this, what do you expect the distance values to look like? Think about the scale of Brazil.
  • Run the script again:
python sales_prediction.py

Close the plot windows as they appear. After the baseline model output, you see a table of five rows showing seller cities, customer cities, and the calculated distance in kilometers between them. Some distances are small (local orders) and others span hundreds or thousands of kilometers.

Seeing a SettingWithCopyWarning?

  • Make sure you included the df_model = df_model.copy() line before assigning the new column. This creates an independent DataFrame that pandas can safely modify.

Need more help? Help me fix the SettingWithCopyWarning in my distance calculation.

Encode seller state as a numeric feature

Distance captures proximity, but different Brazilian states also have distinct market dynamics. Encoding the Seller State column as dummy variables lets the model learn regional patterns beyond just distance.

  • Add these two lines at the bottom of sales_prediction.py:
state_dummies = pd.get_dummies(df_model["Seller State"], prefix="State", drop_first=True)
print(f"\nState dummies added: {state_dummies.shape[1]} columns")

What does this code do?

pd.get_dummies() converts each unique state into its own binary column (1 if the store is in that state, 0 otherwise). The drop_first=True parameter drops one state to avoid multicollinearity. This is the same technique you used earlier for Store Kind.

  • Save sales_prediction.py and run the script one more time:
python sales_prediction.py

After the distance sample, you see a line like State dummies added: X columns, confirming that each unique seller state now has its own binary feature column ready for the model.

✔️ Awesome, I've got everything!

Great. Double check you have saved sales_prediction.py with all your Step 4 additions.

ⓧ I'd like to double check the full code

Your sales_prediction.py is now over 100 lines. Scroll through and confirm these sections are present in order:

  • Imports at the top (pandas, numpy, geopandas, matplotlib, seaborn, sklearn).
  • Step 2 section: loading the CSV, cleaning missing geo values, EDA plots, and printing unique states/store kinds.
  • Step 3 section: baseline_features list, store_kind_dummies, train/test split, lr_baseline model, R2/MAE print, and coefficient table.
  • Step 4 section: gdf GeoDataFrame creation and store_map.png scatter plot.
  • haversine_km function definition and customer_store_distance_km column added to df_model.
  • state_dummies created with pd.get_dummies and shape printed.

You now have two powerful geographic features ready for modeling: the real distance between each customer and their store, and regional state encoding. In the next step, you will plug these features into a new model and compare it head-to-head with your baseline to see if location actually moves the needle on prediction accuracy.

Build an Improved Model with Geographic Features and Compare

In the previous step, you engineered two powerful geographic features: the customer-to-store distance in kilometers and the seller's state encoded as dummy columns. Your feature matrix is ready to go.

Now comes the payoff. You will plug those geographic features into the model and measure whether location actually improves predictions. If it does, you will have quantified proof that geography matters for retail sales.

In this step, get ready to:
  • Train a geo-enhanced Linear Regression and compare it against the baseline.
  • Train a Random Forest model and visualize which features drive predictions.
  • Print a final results table comparing all three models side-by-side.
Build the geo-enhanced feature matrix and train a linear model

The baseline model from Step 3 only knew about store attributes like area and income. It had no idea where customers and stores were located. By combining the original features with customer_store_distance_km and the state dummy columns, you create a richer feature set that captures location.

Before you run this, do you think adding geographic columns will produce a higher R2 than the baseline? Why or why not?

  • In sales_prediction.py, add the following code at the bottom of the file to build the enhanced feature matrix, train a linear model, and compare it to the baseline:
# --------------------------------------------------
# STEP 5: Improved Model with Geographic Features
# --------------------------------------------------
X_geo = pd.concat(
    [
        df_model[baseline_features],
        store_kind_dummies.loc[df_model.index],
        df_model[["customer_store_distance_km"]],
        state_dummies,
    ],
    axis=1,
)

X_train_g, X_test_g, y_train_g, y_test_g = train_test_split(
    X_geo, y.loc[X_geo.index], test_size=0.2, random_state=42
)

lr_geo = LinearRegression()
lr_geo.fit(X_train_g, y_train_g)
y_pred_geo = lr_geo.predict(X_test_g)
r2_geo = r2_score(y_test_g, y_pred_geo)
mae_geo = mean_absolute_error(y_test_g, y_pred_geo)

print(f"\n--- Comparison ---")
print(f"Baseline Linear R2: {r2_baseline:.4f}  |  Geo-Enhanced Linear R2: {r2_geo:.4f}")
print(f"Baseline Linear MAE: {mae_baseline:.4f} |  Geo-Enhanced Linear MAE: {mae_geo:.4f}")

What does this code do?

  • X_geo concatenates the original store features, the store kind dummies, the new customer_store_distance_km column, and all the state dummy columns into one feature matrix.
  • store_kind_dummies.loc[df_model.index] aligns the store kind dummies to the same rows as df_model, since rows were dropped during cleaning.
  • The train/test split uses the same random_state=42 so the comparison against the baseline is fair.
  • The print statements show the baseline and geo-enhanced R2 and MAE side by side, so you can immediately see whether location helped.
  • Save sales_prediction.py.
  • Run the script by running this command:
python sales_prediction.py

Scroll down past the earlier output. Under the --- Comparison --- heading, you should see the baseline R2 and MAE next to the geo-enhanced R2 and MAE. The geo-enhanced model should have a higher R2 and lower MAE.

Not seeing an improvement?

  • Make sure you saved sales_prediction.py before running it.
  • Check that X_geo includes both customer_store_distance_km and state_dummies in the pd.concat call. Missing either one reduces the geographic signal.
  • If you see a KeyError, confirm that Step 4's code creating customer_store_distance_km and state_dummies is still in your file above the Step 5 section.

Still stuck? Ask for help.

Train a Random Forest and visualize feature importance

Linear Regression assumes a straight-line relationship between features and the target. A Random Forest can capture nonlinear patterns, like the idea that distance matters more after a certain threshold. It also produces feature importances that tell you exactly which variables drive predictions.

Before you add this code, predict whether a Random Forest will outperform the geo-enhanced linear model you just trained.

  • Add the following code below the comparison print statements to train a Random Forest and plot the top 15 feature importances:
rf_geo = RandomForestRegressor(n_estimators=100, random_state=42, n_jobs=-1)
rf_geo.fit(X_train_g, y_train_g)
y_pred_rf = rf_geo.predict(X_test_g)
r2_rf = r2_score(y_test_g, y_pred_rf)
mae_rf = mean_absolute_error(y_test_g, y_pred_rf)

importances = rf_geo.feature_importances_
feat_imp = pd.DataFrame({
    "Feature": X_geo.columns,
    "Importance": importances,
}).sort_values("Importance", ascending=True).tail(15)

fig, ax = plt.subplots(figsize=(10, 6))
ax.barh(feat_imp["Feature"], feat_imp["Importance"])
ax.set_title("Top 15 Feature Importances (Random Forest)")
ax.set_xlabel("Importance")
plt.tight_layout()
plt.savefig("feature_importance.png", dpi=150)
plt.show()

What does this code do?

  • RandomForestRegressor(n_estimators=100) trains an ensemble of 100 decision trees. Each tree learns a slightly different pattern, and the forest averages their predictions for a more robust result.
  • n_jobs=-1 uses all available CPU cores to speed up training.
  • feature_importances_ reports how much each feature contributed to reducing prediction error across all 100 trees. Higher values mean that feature matters more.
  • The horizontal bar chart makes it easy to spot whether geographic features like customer_store_distance_km or state columns rank among the top drivers.
  • Save sales_prediction.py and run the script again:
python sales_prediction.py

A horizontal bar chart pops up showing the top 15 features ranked by importance. Look for customer_store_distance_km or any State_ columns in the chart. The chart is also saved as feature_importance.png.

Chart not appearing?

  • If you are running the script from the terminal and no window appears, check that your matplotlib backend supports interactive display. You can still verify the chart by opening feature_importance.png from your project folder.
  • If you see a NameError for X_train_g or y_train_g, make sure the Chunk 1 code with train_test_split is above this block in your file.

Need help? Ask here.

Print the final model comparison

You have trained three models so far: the baseline linear model, the geo-enhanced linear model, and the geo-enhanced Random Forest. A formatted results table puts all three side-by-side so you can see the full progression.

  • Add this code at the bottom of sales_prediction.py to print the final comparison table:
print(f"\n{'='*55}")
print(f"{'Model':<30} {'R2':>10} {'MAE':>12}")
print(f"{'-'*55}")
print(f"{'Baseline Linear':<30} {r2_baseline:>10.4f} {mae_baseline:>12.4f}")
print(f"{'Geo-Enhanced Linear':<30} {r2_geo:>10.4f} {mae_geo:>12.4f}")
print(f"{'Geo-Enhanced Random Forest':<30} {r2_rf:>10.4f} {mae_rf:>12.4f}")
print(f"{'='*55}")

What does this code do?

This prints a neatly formatted ASCII table with all three models' R2 and MAE scores. The :<30 and :>10 formatting aligns columns so the numbers line up for easy comparison.

Before you run the script, predict which row of the table will show the highest R2 and the lowest MAE. Will the progression from baseline to geo-enhanced to Random Forest be consistent?

  • Save sales_prediction.py and run it one more time:
python sales_prediction.py

Scroll to the bottom of the output. You should see a formatted table with all three models listed. The Geo-Enhanced Random Forest should have the highest R2 and the lowest MAE, confirming that both geographic features and nonlinear modeling contributed to better predictions.

Table not printing at the bottom?

  • If the script exits before reaching the table, check for errors in the Random Forest block above it. The table code depends on r2_rf and mae_rf being defined.
  • If you see a NameError for r2_baseline or mae_baseline, confirm that your Step 3 baseline code is still intact above Step 5.

Need help debugging? Ask here.

✔️ Awesome, I've got everything!

Great. Double check that you have saved sales_prediction.py and that the final summary table printed cleanly in your terminal.

ⓧ I'd like to double check the full code

Your sales_prediction.py is now over 30 lines, so scroll through it and confirm these sections appear in order:

  • Imports at the top (pandas, numpy, geopandas, matplotlib, seaborn, sklearn).
  • Step 2: Load, clean, and explore (CSV load, dropna, histogram, heatmap).
  • Step 3: Baseline regression (baseline_features list, X_baseline, train/test split, lr_baseline, coefficients).
  • Step 4: Geographic feature engineering (gdf GeoDataFrame, store map, haversine_km function, customer_store_distance_km column, state_dummies).
  • Step 5: Improved model (X_geo concat, lr_geo comparison, rf_geo with feature importance chart, final summary table).

You now have quantified proof that geography matters for retail sales prediction. Your results table shows the full progression from a naive baseline to an informed, nonlinear model. Next up, there is an optional Secret Mission that digs into where the model struggles most.

Secret mission

Build a Geographic Prediction Error Heatmap

Your model performs well overall, but does it predict equally well across all of Brazil? Some regions might have sparse data or unique market dynamics that throw off predictions. In this mission, you will build a geographic error heatmap that reveals exactly where your model struggles.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine, so there are no ongoing cloud costs or paid services to worry about. Decide whether to keep your project files for future reference or delete them to reclaim disk space.

Resources you used:

  • The retail-sales-prediction project directory containing sales_prediction.py, the dataset CSV, and generated plot images.
  • The venv virtual environment inside the project directory with all installed libraries (pandas, numpy, geopandas, scikit-learn, matplotlib, seaborn).

Keep everything running

No action needed. Choose this if you want to keep experimenting with the dataset, tweak your models, or extend the project further.

  • Your retail-sales-prediction directory and virtual environment stay on your Desktop for future use.
  • All generated plots (eda_plots.png, store_map.png, feature_importance.png, error_heatmap.png) remain available for your portfolio or presentations.

Pause - I'll come back to this later

Deactivate the virtual environment to free up your terminal session. Your files and installed packages stay on disk for when you return.

  • Deactivate the virtual environment by running this command in your terminal:
deactivate
  • When you come back, navigate to the retail-sales-prediction folder and reactivate with source venv/bin/activate (macOS/Linux) or venv\Scripts\activate (Windows).

Delete - I don't want to use this again

Remove the entire project directory to reclaim disk space. This deletes your script, dataset, virtual environment, and all generated plots.

  • Deactivate the virtual environment first by running:
deactivate
  • Navigate to your Desktop by running:
cd ~/Desktop
  • Delete the project directory. On macOS/Linux, run:
rm -rf retail-sales-prediction
  • On Windows, run:
rmdir /s /q retail-sales-prediction
  • Confirm the directory is gone by running ls on your Desktop. You should no longer see retail-sales-prediction in the output.

That's a Wrap!

That's a Wrap!

Nice work! You built a complete retail sales prediction pipeline that turns raw geographic coordinates into meaningful features, proving that location data measurably improves model accuracy across 40,000+ Brazilian marketplace orders.

You've learned how to:

  • Loaded and cleaned a real-world retail dataset, then built a baseline regression model using only store attributes to establish a performance floor you could measure against.
  • Engineered geospatial features with GeoPandas, calculating customer-to-store distances using the Haversine formula and mapping store sales performance across Brazil.
  • Compared three models side-by-side (Baseline Linear, Geo-Enhanced Linear, Geo-Enhanced Random Forest) and built a feature importance chart proving that geographic variables like distance and region drive predictions.
  • Secret Mission: Built a geographic error heatmap revealing which Brazilian regions the model predicts best and worst.

Ready to quiz yourself?