You’re right, I removed it. I guess the decision to choose heatmap coloured by $ or by % depends a bit on where what is happening in the bigger picture. Is it a problem that almost 90% of the books ordered and paid by Gift Card are cancelled.? Yes, that’s a lot, No, Books are revenue wise not very interesting.
Hi Marianne,
df[‘Customer_ID’] = df[‘Customer Name’] + " - " + df[‘Customer Location’]
But I did for this image, not for the app I shared. Not only Chris Martin, there others kkk. As you said or are frequente travelers, or are resellers. I do not know.
thank you Adam, for take the time to play around with the app.
If you combine name + location as customer id, the order route timeline has max 7 steps (regarding per customer) often less, I calculated that and had a look, strange behaviour noticed. ![]()
Do you mean order by Date/Customer?or Status /Order Id, Date?
Not sure, I mean if you assign a customer ID based on name + location and view the rows belonging to a unique customer id across the complete dataset.
Sales Dashboard Summary
The dashboard offers a clear and interactive subplots view of sales data, styled with a modern dark theme.
Users can filter all visualizations by product instantly using a sleek dropdown at the top.
The left chart visualizes the monthly sales trend with blue tones, highlighting changes and seasonality.
On the right, a bubble scatterplot shows sales by product and customer location, with bubble size representing sales and color indicating average price—all bubbles have white outlines for full visibility.
This design keeps every value readable on a dark background and is tailored for business, analytics, and presentation-ready insights.
Summary
import dash
import dash_bootstrap_components as dbc
from dash import dcc, html, Input, Output
from plotly.subplots import make_subplots
import plotly.graph_objects as go
# Load and preprocess data
df = pd.read_csv("amazon_sales_data.csv")
df['Date'] = pd.to_datetime(df['Date'], format="%d-%m-%y")
df['Month'] = df['Date'].dt.to_period('M').astype(str)
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.DARKLY])
server = app.server
app.layout = dbc.Container([
html.H2("Sales Analysis Dashboard", className="text-center my-3", style={"color": "#3ab4f2"}),
dbc.Row([
dbc.Col([
dbc.Label("Product Filter", style={"color": "#3ab4f2"}),
dbc.Select(
id="product-select",
options=[{"label": "All Products", "value": "All"}] +
[{"label": p, "value": p} for p in sorted(df['Product'].unique())],
value="All",
style={
"backgroundColor": "#222c37",
"color": "#3ab4f2",
"border": "1px solid #3ab4f2"
}
),
], md=5, lg=3)
], className="mb-4 justify-content-center"),
dbc.Row([
dbc.Col([
dcc.Graph(id="sales-charts", style={'height': '560px'})
])
]),
], style={"backgroundColor": "#191c24", "minHeight": "100vh", "paddingBottom": "30px"})
@app.callback(
Output("sales-charts", "figure"),
Input("product-select", "value"),
)
def update_charts(product_sel):
# Data filtering
if product_sel == "All":
dff = df.copy()
else:
dff = df[df["Product"] == product_sel]
# --- LEFT: Monthly Sales Blue Line Chart ---
monthly = dff.groupby("Month")["Total Sales"].sum().reset_index()
# --- RIGHT: Product vs. Customer Location scatter ---
top_locations = dff.groupby("Customer Location")["Total Sales"].sum().nlargest(10).index
scatter_df = dff[dff["Customer Location"].isin(top_locations)]
agg = scatter_df.groupby(['Product', 'Customer Location']).agg(
total_sales=('Total Sales', 'sum'),
avg_price=('Price', 'mean'),
order_count=('Order ID', 'count')
).reset_index()
product_order = list(agg['Product'].unique())
location_order = list(agg['Customer Location'].unique())
fig = make_subplots(
rows=1, cols=2,
column_widths=[0.43, 0.57],
subplot_titles=[
"Monthly Total Sales Trend",
"Product vs. Location Bubble Plot"
],
specs=[[{"type": "scatter"}, {"type": "scatter"}]]
)
# --- Left chart: Blue time trend ---
fig.add_trace(
go.Scatter(
x=monthly['Month'],
y=monthly['Total Sales'],
mode="lines+markers",
name="Total Sales",
line=dict(color="#2196f3", width=4),
marker=dict(color="#3ab4f2", size=10)
),
1, 1
)
# --- Right chart: High-visibility colored bubbles, white outline ---
fig.add_trace(
go.Scatter(
x=agg["Product"],
y=agg["Customer Location"],
mode="markers",
marker=dict(
size=(agg["total_sales"] / agg["total_sales"].max() * 38 + 10).clip(10, 48),
color=agg["avg_price"],
colorscale="Viridis", # Change to Cividis, YlGnBu, etc. (these are all visible on dark bg)
showscale=True,
colorbar=dict(title="Avg. Price"),
line=dict(width=2, color='#fff')
),
text=[f"Product: {p}<br>Location: {l}" for p, l in zip(agg["Product"], agg["Customer Location"])],
customdata=agg[["total_sales", "order_count"]],
hovertemplate=(
"%{text}<br>"
"Total Sales: %{customdata[0]:$,.0f}<br>"
"Order Count: %{customdata[1]:.0f}<br>"
"Avg. Price: %{marker.color:$,.2f}<extra></extra>"
),
name="Product-Location"
),
1, 2
)
fig.update_layout(
template="plotly_dark",
font=dict(color="#3ab4f2"),
height=540,
margin=dict(l=40, r=30, t=60, b=40),
title="Sales Overview (Trend & Product-Location Scatter)"
)
fig.update_xaxes(title="Month", row=1, col=1)
fig.update_yaxes(title="Total Sales ($)", row=1, col=1)
fig.update_xaxes(title="Product", row=1, col=2, categoryorder='array', categoryarray=product_order)
fig.update_yaxes(title="Customer Location", row=1, col=2, categoryorder='array', categoryarray=location_order)
return fig
if __name__ == "__main__":
app.run(debug=True)```
HI @Ester ,
That first scatter plot is very helpful in understanding product sales per location. Good job!
The scatter plot in the second image is also helpful, but you might find it easier to visualize the data with a bar chart because there is only one product at a time.
Do you have the link to your app?
Thanl you! The second chart is the same, only one product is selected. Unfortunately I didn’t have time for the app right now, but I’ll upload it after FF.
I didn’t have time this week to create a dashboard but am sharing a heatmap of sales by customer and location. As others have mentioned it looks like same customers are popping up in different cities. What I found was the data set has 10 unique customers, 10 unique locations and 92 unique combinations of customer and location.
Here is a screenshot:
Here is the code:
import polars as pl
# Dataset has 10 unique customers & locations, 92 unique customer/locatio pairs
#----- LOAD AND CLEAN THE DATASET
df = (
pl.read_csv(
'amazon_sales_data.csv',
)
.rename( # upper case all column names, replace spaces with underscores
lambda c:
c.upper() # column names to upper case
.replace(' ', '_') # blanks replaced with underscores
)
.with_columns( # clean up DATE column and convert to pl.Date
DATE = pl.col('DATE')
.str.replace_all('-', '/')
.str.replace_all('/25', '/2025')
.str.to_date(format='%d/%m/%Y')
)
.rename({
'ORDER_ID' : 'ID',
'CUSTOMER_NAME' : 'CUSTOMER',
'TOTAL_SALES' : 'TOTAL',
'CUSTOMER_LOCATION' : 'LOCATION',
'PAYMENT_METHOD' : 'METHOD'
})
.group_by(['CUSTOMER', 'LOCATION']).agg(pl.col('TOTAL').sum())
)
# Convert Polars DataFrame to a dictionary for Plotly
heatmap_data = df.to_dict(as_series=False)
# Create the heatmap
fig = px.density_heatmap(
x=heatmap_data['CUSTOMER'],
y=heatmap_data['LOCATION'],
z=heatmap_data['TOTAL'],
labels={"x": 'CUSTOMER', "y": 'LOCATION', "z": 'TOTAL SPEND'},
color_continuous_scale='greens',
text_auto='$,d'# text_auto=".2f",
# title='SALES BY CUSTOMER, LOCATION'
)
fig.update_layout(
title=dict(text='Sales by Cusomer, Location')
)
fig.update(layout_coloraxis_showscale=False)
fig.show()


