0. Import Libraries:

# STEP 0: Import libraries
import numpy as numpy
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from itertools import combinations
from collections import Counter

import warnings
warnings.filterwarnings('ignore')

# Set style for better visualizations
plt.style.use('seaborn-v0_8')
# sns.set_palette("husl")
sns.color_palette("Blues", as_cmap=True)

print("Libraries imported successfully!")

Output:

Libraries imported successfully!

1. Data Collection

# Step 0: Data collection
# Concatenate all 12 (monthly) datasets into a single dataset.
files = ['Sales_January_2019.csv', 'Sales_February_2019.csv', 'Sales_March_2019.csv', \
        'Sales_April_2019.csv', 'Sales_May_2019.csv', 'Sales_June_2019.csv', \
        'Sales_July_2019.csv', 'Sales_August_2019.csv', 'Sales_September_2019.csv', \
        'Sales_October_2019.csv', 'Sales_November_2019.csv', 'Sales_December_2019.csv']

dfs = pd.DataFrame()
for file in files:
    temp = pd.read_csv(file, delimiter=",")
    dfs = pd.concat([dfs, temp], ignore_index=True)

# Export the merged raw dataset to a CSV file (index excluded).
# Data cleaning and preprocessing steps are still pending at this stage.
export_as = 'Sales_2019.csv'
dfs.to_csv(export_as, index=False)
print(f"Datasets concatinated and exported as: {export_as}")

Output:

Datasets concatinated and exported as: Sales_2019.csv

2. Data Inspection

# Step 1: Load and Inspect Data
print("--- Loading Data ---")
file_path = 'Sales_2019.csv'

df = pd.read_csv(file_path, delimiter=',', dtype=str)
print("Dataset loaded successfully.\n")

print(f"Dataset Shape: {df.shape}")
print(f"Columns: {list(df.columns)}")

# Display first 5 rows
print("\nFirst 5 rows:")
print(df.head())
# df.head()

Output:

--- Loading Data ---
Dataset loaded successfully.

Dataset Shape: (186850, 6)
Columns: ['Order ID', 'Product', 'Quantity Ordered', 'Price Each', 'Order Date', 'Purchase Address']

First 5 rows:
  Order ID                   Product Quantity Ordered Price Each  \
0   141234                    iPhone                1        700   
1   141235  Lightning Charging Cable                1      14.95   
2   141236          Wired Headphones                2      11.99   
3   141237          27in FHD Monitor                1     149.99   
4   141238          Wired Headphones                1      11.99   

       Order Date                       Purchase Address  
0  01/22/19 21:25        944 Walnut St, Boston, MA 02215  
1  01/28/19 14:15       185 Maple St, Portland, OR 97035  
2  01/17/19 13:33  538 Adams St, San Francisco, CA 94016  
3  01/05/19 20:33     738 10th St, Los Angeles, CA 90001  
4  01/25/19 11:59          387 10th St, Austin, TX 73301  

1. Check Dataset Info

# Check data types and basic info
print("Data Types and Info:")
df.info()

Output:

Data Types and Info:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 186850 entries, 0 to 186849
Data columns (total 6 columns):
 #   Column            Non-Null Count   Dtype 
---  ------            --------------   ----- 
 0   Order ID          186305 non-null  object
 1   Product           186305 non-null  object
 2   Quantity Ordered  186305 non-null  object
 3   Price Each        186305 non-null  object
 4   Order Date        186305 non-null  object
 5   Purchase Address  186305 non-null  object
dtypes: object(6)
memory usage: 8.6+ MB

2. Get Dataset Description

print(df.describe())
# df.describe()

Output:

        Order ID               Product Quantity Ordered Price Each  \
count     186305                186305           186305     186305   
unique    178438                    20               10         24   
top     Order ID  USB-C Charging Cable                1      11.95   
freq         355                 21903           168552      21903   

        Order Date  Purchase Address  
count       186305            186305  
unique      142396            140788  
top     Order Date  Purchase Address  
freq           355               355  

Seems all columns object/string, as they are not cleaned yet.

3. Data Cleaning

1. Handle Missing Values

# Step 2: Data Cleaning
print("\n--- 2. Cleaning Data ---\n")

# Create a copy for cleaning
df_clean = df.copy()

# Check for missing values
x = df_clean.isnull().sum().value_counts()
# y = df_clean.isnull().sum().count()
# z = df_clean.isnull().sum().sum()

for rows, cols in x.items():
    print(f"Missing Values: \n{rows*cols} found in [{rows} rows * {cols} columns] found.")

# Preview missing values
missing_values = df_clean[df_clean.isna().any(axis=1)]
print("\nPreview of Missing Values:\n", missing_values.head(10), "\n") # comment to stop seeing the preview of missing values

# 2.1 Drop rows where ALL columns are missing
df_clean = df_clean.dropna(how='all')
print(f"Removed {rows * cols} missing values, from {rows} rows and {cols} columns.\n")

Output:


--- 2. Cleaning Data ---

Missing Values: 
3270 found in [545 rows * 6 columns] found.

Preview of Missing Values:
      Order ID Product Quantity Ordered Price Each Order Date Purchase Address
664       NaN     NaN              NaN        NaN        NaN              NaN
678       NaN     NaN              NaN        NaN        NaN              NaN
797       NaN     NaN              NaN        NaN        NaN              NaN
876       NaN     NaN              NaN        NaN        NaN              NaN
1299      NaN     NaN              NaN        NaN        NaN              NaN
1491      NaN     NaN              NaN        NaN        NaN              NaN
1699      NaN     NaN              NaN        NaN        NaN              NaN
3047      NaN     NaN              NaN        NaN        NaN              NaN
3082      NaN     NaN              NaN        NaN        NaN              NaN
3584      NaN     NaN              NaN        NaN        NaN              NaN 

Removed 3270 missing values, from 545 rows and 6 columns.

2. Handle Duplicate Values

# Check for duplicates
duplicates = df_clean.duplicated().sum()
print(f"Duplicate rows: {duplicates} found.")

# Remove duplicates if any
if duplicates > 0:
    df_clean = df_clean.drop_duplicates()
    print(f"Removed {duplicates} duplicate rows.")

Output:

Duplicate rows: 618 found.
Removed 618 duplicate rows.

Removing garbage/repeated header

# 2.2 Remove 'garbage' rows (repeated headers)
df_clean = df_clean[df_clean['Order Date'] != 'Order Date']
print("Garbage/duplicate header rows removed.\n")

    # # Find duplicate header rows
    # mask = (df_clean == list(df_clean.columns)).all(axis=1)

    # # Drop them
    # df_clean = df_clean[~mask]
    # # print(dfx.shape[0])

Output:

Garbage/duplicate header rows removed.

3. Renaming Columns

# Rename columns: for avoiding confusion later
df_clean.columns = df_clean.columns.str.replace(' ', '_').str.lower() # dpm
    # df_clean.columns = df_clean.columns.str.lower() # dpm
    # df_clean.columns = df_clean.columns.str.replace(' ', '_') # dpm

print("Columns renamed successfully!")

Output:

Columns renamed successfully!

4. Fixing Data Types

# 2.3 Fix data types
df_clean['quantity_ordered'] = pd.to_numeric(df_clean['quantity_ordered'], errors='coerce')
df_clean['price_each'] = pd.to_numeric(df_clean['price_each'], errors='coerce')
    # df_clean['quantity_ordered'] = df_clean['quantity_ordered'].astype(int)
    # df_clean['price_each'] = df_clean['price_each'].astype(float)

# Add 'format' to silence the warning and speed up processing
# Format '%m/%d/%y %H:%M' matches '01/22/19 21:25'
df_clean['order_date'] = pd.to_datetime(df_clean['order_date'], format='%m/%d/%y %H:%M', errors='coerce') # Convert to datetime safely

print("Fixed data types (of numeric and datatime columns).")
print("Data Cleaning performed successfully.")

print("\nDataset Info:")
print(df_clean.info())
# df_clean.head()

Output:

Fixed data types (of numeric and datatime columns).
Data Cleaning performed successfully.

Dataset Info:
<class 'pandas.core.frame.DataFrame'>
Index: 185686 entries, 0 to 186849
Data columns (total 6 columns):
 #   Column            Non-Null Count   Dtype         
---  ------            --------------   -----         
 0   order_id          185686 non-null  object        
 1   product           185686 non-null  object        
 2   quantity_ordered  185686 non-null  int64         
 3   price_each        185686 non-null  float64       
 4   order_date        185686 non-null  datetime64[ns]
 5   purchase_address  185686 non-null  object        
dtypes: datetime64[ns](1), float64(1), int64(1), object(3)
memory usage: 9.9+ MB
None

5. Cleaned Dataset Description

print(df_clean.describe())
# df_clean.describe()

Output:

       quantity_ordered     price_each                     order_date
count     185686.000000  185686.000000                         185686
mean           1.124544     184.519255  2019-07-18 21:32:06.298051328
min            1.000000       2.990000            2019-01-01 03:07:00
25%            1.000000      11.950000            2019-04-16 20:55:15
50%            1.000000      14.950000            2019-07-17 20:11:00
75%            1.000000     150.000000            2019-10-26 08:00:00
max            9.000000    1700.000000            2020-01-01 05:13:00
std            0.443069     332.843838                            NaN

4. Feature Engineering

1. Rename columns

# STEP 3: Feature Engineering
print("\n--- 3. Feature Engineering ---")

df_featured = df_clean.copy()

# Rename column: quantity_ordered ---> quantity
df_featured.rename(columns={'quantity_ordered':'quantity', 'price_each':'price'}, inplace=True)
print("Column names changed.")

Output:

--- 3. Feature Engineering ---
Column names changed.

2. Add extra columns for EDA

# 3.2 Calculate and add (total) sales (Revenue) Column 
df_featured['sales'] = df_featured['quantity'] * df_featured['price']

# Extract additional features
df_featured['month'] = df_featured['order_date'].dt.month # Add month Column
df_featured['date'] = df_featured['order_date'].dt.day # Add date column
df_featured['day'] = df_featured['order_date'].dt.day_name() # Add day column
df_featured['hour'] = df_featured['order_date'].dt.hour # 3.4 Add hour column

# 3.3 Add city Column (Extracting City and State)
def get_city(address):
    return address.split(',')[1]

def get_state(address):
    return address.split(',')[2].split(' ')[1]

df_featured['city'] = df_featured['purchase_address'].apply(lambda x: f"{get_city(x)} ({get_state(x)})")
    # # Extract city from address
    # df_featured['city'] = df_featured['purchase_address'].str.split(',').str[1].str.strip()

print("Features Added: sales, month, date, day, hour, city")

Output:

Features Added: sales, month, date, day, hour, city

2. Remove unnecessary columns

# Remove unnecessary columns
# df_featured = df_featured.drop(columns=['purchase_address'])
# print("Features Removed: order_date, purchase_address")

print("\nFeature Engineering performed successfully.\n")

print("Dataset after Feature Engineering:")
print(df_featured.head(8))
# df_featured.head(8)

Output:


Feature Engineering performed successfully.

Dataset after Feature Engineering:
  order_id                   product  quantity   price          order_date  \
0   141234                    iPhone         1  700.00 2019-01-22 21:25:00   
1   141235  Lightning Charging Cable         1   14.95 2019-01-28 14:15:00   
2   141236          Wired Headphones         2   11.99 2019-01-17 13:33:00   
3   141237          27in FHD Monitor         1  149.99 2019-01-05 20:33:00   
4   141238          Wired Headphones         1   11.99 2019-01-25 11:59:00   
5   141239    AAA Batteries (4-pack)         1    2.99 2019-01-29 20:22:00   
6   141240    27in 4K Gaming Monitor         1  389.99 2019-01-26 12:16:00   
7   141241      USB-C Charging Cable         1   11.95 2019-01-05 12:04:00   

                         purchase_address   sales  month  date       day  \
0         944 Walnut St, Boston, MA 02215  700.00      1    22   Tuesday   
1        185 Maple St, Portland, OR 97035   14.95      1    28    Monday   
2   538 Adams St, San Francisco, CA 94016   23.98      1    17  Thursday   
3      738 10th St, Los Angeles, CA 90001  149.99      1     5  Saturday   
4           387 10th St, Austin, TX 73301   11.99      1    25    Friday   
5  775 Willow St, San Francisco, CA 94016    2.99      1    29   Tuesday   
6      979 Park St, Los Angeles, CA 90001  389.99      1    26  Saturday   
7     181 6th St, San Francisco, CA 94016   11.95      1     5  Saturday   

   hour                 city  
0    21          Boston (MA)  
1    14        Portland (OR)  
2    13   San Francisco (CA)  
3    20     Los Angeles (CA)  
4    11          Austin (TX)  
5    20   San Francisco (CA)  
6    12     Los Angeles (CA)  
7    12   San Francisco (CA)  

5. Exploratory Data Analysis (EDA)

# STEP 4: Exploratory Data Analysis (EDA)
# --- Q0: Describe Total Sales ---
total_sales = df_featured['sales'].sum()
average_sales = df_featured['sales'].mean()
min_sales = df_featured['sales'].min()
max_sales = df_featured['sales'].max()

print("1. Total Sales:", f"{total_sales:.2f}")
print("2. Average Sales:", f"{average_sales:.2f}")
print("3. Minimum Sales:", f"{min_sales:.2f}")
print("4. Maximum Sales:", f"{max_sales:.2f}")

# print(df_featured['sales'].describe())

Output:

1. Total Sales: 34465537.94
2. Average Sales: 185.61
3. Minimum Sales: 2.99
4. Maximum Sales: 3400.00

1. Quantity Ordered Distribution

plt.figure(figsize=(12, 6))
plt.hist(df_featured['quantity'])
plt.xlabel('Quantity Ordered')
plt.ylabel('Order Counts')
plt.title('Quantity Ordered Distribution')
plt.grid(axis='y', linestyle='--')
plt.show()

Output: quantity-ordered-distribution

2. Each Price Distribution

plt.figure(figsize=(12, 6))
plt.hist(df_featured['price'])
plt.xlabel('Price')
plt.ylabel('Number of Orders')
plt.title('Price Distribution')
plt.grid(axis='y', linestyle='--')
plt.show()

Output: each-price-distribution

6. Quantity vs Price Analysis

# --- Q5: Quantity vs Price Analysis ---
print("5. Plotting Quantity vs Price...")

# Group data
product_group = df_featured.groupby('product')

quantity = product_group['quantity'].sum()
prices = product_group['price'].mean()
products = [product for product, df_product in product_group]

# Create the figure
fig, ax1 = plt.subplots(figsize=(12, 6))

# Plot Bar Chart (Quantity) on Left Axis
ax2 = ax1.twinx()
ax1.bar(products, quantity) # color='#206fb4', alpha=0.7

# Plot Line Chart (Price) on Right Axis
ax2.plot(products, prices, linestyle='--', marker='s', color='tomato') 

# Label Axes
ax1.set_xlabel('Product Name')
ax1.set_ylabel('Quantity Ordered', color='g')
ax2.set_ylabel('Price ($)', color='b')

# FIX: Explicitly set the tick positions before setting labels
ax1.set_xticks(range(len(products)))
ax1.set_xticklabels(products, rotation=90, size=8)

plt.title('Quantity Ordered vs. Price')
plt.show()

quantity-vs-price-1

8. Price vs Quantity Ordered

# Price vs Order correlation
# --- Correlation Scatter Plot ---
print("\n6. Plotting Price Elasticity (Scatter)...\n")
product_df = pd.DataFrame({'quantity': quantity, 'price': prices}).reset_index()
print(product_df)

plt.figure(figsize=(10, 6))
plt.scatter(product_df['price'], product_df['quantity'])
# sns.regplot(x='price', y='quantity', data=product_df, logx=True, scatter_kws={"s": 100}, line_kws={"color": "#206fb4"})

plt.title('Price vs. Quantity Ordered (Demand Curve)')
plt.xlabel('Price ($)')
plt.ylabel('Quantity Sold')
plt.grid(True)
plt.show()

Output:

6. Plotting Price Elasticity (Scatter)...

                       product  quantity    price
0                 20in Monitor      4126   109.99
1       27in 4K Gaming Monitor      6239   389.99
2             27in FHD Monitor      7541   149.99
3       34in Ultrawide Monitor      6192   379.99
4        AA Batteries (4-pack)     27615     3.84
5       AAA Batteries (4-pack)     30986     2.99
6     Apple Airpods Headphones     15637   150.00
7   Bose SoundSport Headphones     13430    99.99
8                Flatscreen TV      4813   300.00
9                 Google Phone      5529   600.00
10                    LG Dryer       646   600.00
11          LG Washing Machine       666   600.00
12    Lightning Charging Cable     23169    14.95
13          Macbook Pro Laptop      4725  1700.00
14             ThinkPad Laptop      4128   999.99
15        USB-C Charging Cable     23931    11.95
16             Vareebadd Phone      2068   400.00
17            Wired Headphones     20524    11.99
18                      iPhone      6847   700.00

price-order-correlation

3. Sales by Month

# --- Q1: Sales by Month/monthly sales ---
print("\n1. Plotting Sales by Month...")

monthly_sales = df_featured.groupby('month')['sales'].sum()
print(monthly_sales)

months = range(1, 13)
plt.figure(figsize=(12, 6))
# plt.bar(months, monthly_sales, color='skyblue')

# Add count labels above each bar
bars = plt.bar(months, monthly_sales) # color='#206fb4'
for bar in bars:
    height = bar.get_height()
    plt.text(bar.get_x() + bar.get_width()/2, 
             height + 100,  # adjust +100 if needed
             f'{int(height)}', ha='center', va='bottom')
    
plt.ylim(0, monthly_sales.max() * 1.15) # 1.25 if need extra space

plt.xticks(months)
plt.ylabel('Sales in USD ($)')
plt.xlabel('Month')
plt.title('Total Sales by Month')
plt.grid(axis='y', linestyle='--')
plt.show()

Output:

1. Plotting Sales by Month...
month
1     1821413.16
2     2200078.08
3     2804973.35
4     3389217.98
5     3150616.23
6     2576280.15
7     2646461.32
8     2241083.37
9     2094465.69
10    3734777.86
11    3197875.05
12    4608295.70
Name: sales, dtype: float64

sales-by-month

4. Sales by Date

# Sales by Date
monthly_sales = df_featured.groupby('date')['sales'].sum()

print(monthly_sales)

plt.figure(figsize=(12, 6))
# monthly_sales.plot(kind='line', marker='s', linewidth=2, markersize=8)

plt.plot(monthly_sales.index, monthly_sales.values, 
         marker='s', linewidth=1.3, markersize=6.2)

# Add numbers near each marker
# for x, y in zip(monthly_sales.index, monthly_sales.values):
#     plt.text(x, y, f"{y:.0f}", fontsize=10, ha='center', va='bottom')

plt.title('Daily Sales Trend (2019)')
plt.xlabel('Month')
plt.ylabel('Total Sales ($)')
plt.grid(axis="both", linestyle=":", alpha=0.4, color="g",)
plt.xticks(range(1, 32))
plt.show()

Output:

fsdfsdfdfsf

each-price-distribution

5. Sales by Day

# --- Q1: Sales by Day / Daily Sales ---
print("\n1. Plotting Sales by Day...")

weekday_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
df_featured['day'] = pd.Categorical(df_featured['day'], categories=weekday_order, ordered=True)

# Daily aggregation
daily_sales = df_featured.groupby('day')['sales'].sum().sort_index()
print(daily_sales)


plt.figure(figsize=(12, 6))

# Main pie
wedges, texts, autotexts = plt.pie(
    daily_sales,
    labels=daily_sales.index,
    autopct=lambda pct: f"{pct:.1f}%\n({int(pct/100 * daily_sales.sum())})",
    startangle=90,
    counterclock=False,   # <-- start clockwise
    textprops={'fontsize': 12, 'fontweight': 'bold'}
)

# Create donut hole
centre_circle = plt.Circle((0, 0), 0.30, fc='white')
plt.gca().add_artist(centre_circle)

plt.title("Total Sales by Day (Donut Chart)", fontsize=20, fontweight='bold')
plt.tight_layout()
plt.show()

Output:

1. Plotting Sales by Day...
day
Monday       4877588.21
Tuesday      5086275.40
Wednesday    4986823.36
Thursday     4833763.73
Friday       4853642.26
Saturday     4900195.58
Sunday       4927249.40
Name: sales, dtype: float64

sales-by-day-donut-chart

Sales by City

print("2. Plotting Sales by City...")

city_sales = df_featured.groupby('city')['sales'].sum()

# Generate evenly spaced positions
cities = list(city_sales.index)
x_pos = np.arange(len(cities))

plt.figure(figsize=(12, 6))
bars = plt.bar(x_pos, city_sales.values) # Plot bars

# # Add labels above bars
# for bar in bars:
#     height = bar.get_height()
#     plt.text(
#         bar.get_x() + bar.get_width() / 2,
#         height + (height * 0.02),
#         f"{int(height)}",
#         ha="center",
#         va="bottom"
#     )

# Add extra space above tallest bar to avoid cutoff
# plt.ylim(0, city_sales.max() * 1.15) # 1.25 if need extra space

# Add labels above bars
for bar in bars:
    height = bar.get_height()
    plt.text(
        bar.get_x() + bar.get_width() / 2,
        height * 0.5,                 # middle of the bar
        f"{int(height)}",
        ha="center",
        va="center",
        color="white",                # white text looks good inside bars
        fontsize=10,
        fontweight="bold"
    )

# Formatting
plt.xticks(x_pos, cities, rotation=45, ha='right')
plt.ylabel("Sales in USD ($)")
plt.xlabel("City")
plt.title("Total Sales by City")
plt.grid(axis="y", linestyle="--")
plt.tight_layout()
plt.show()

Output:

2. Plotting Sales by City...

sales-by-city

4. Orders by Hour

# --- Q3: Orders by Hour ---
print("3. Plotting Orders by Hour...")
hourly_counts = df_featured.groupby(['hour'])['order_id'].count()
hours = [hour for hour, df_hour in df_featured.groupby('hour')]

plt.figure(figsize=(12, 6))
plt.plot(hours, hourly_counts, marker='o') # color='#206fb4'
plt.xticks(hours)
plt.xlabel('Hour of Day')
plt.ylabel('Number of Orders')
plt.title('Best Time for Advertisements (Orders by Hour)')
plt.grid(True, linestyle=':')
plt.show()

Output:

3. Plotting Orders by Hour...

orders-by-hour

7. Top products by sales/revenue

# Top products by revenue
print("Top 15 Product by sales:\n")

# product_revenue = df_featured.groupby('product')['sales'].sum().sort_values(ascending=False).head(15)
# print(product_revenue)

product_revenue = (
    df_featured.groupby('product')['sales']
    .sum()
    .sort_values(ascending=False)
    .head(15)
)

# # Remove index name and series name
# product_revenue.index.name = None
# product_revenue.name = None

# print(product_revenue.to_string())

# Convert to DataFrame and add serial numbers
df_top = product_revenue.reset_index()
df_top.index = df_top.index + 1   # start index from 1
# df_top.index.name = "S/N"

# Rename columns
df_top.columns = ["Product", "Sales"]

# Print without index name clutter
print(df_top.to_string())

fig, ax = plt.subplots(figsize=(12, 6))
bars = ax.barh(product_revenue.index, product_revenue.values)

# Invert y-axis so largest revenue on top
ax.invert_yaxis()

# Add labels to each bar
for bar in bars:
    width = bar.get_width()
    xpos = width - (product_revenue.max()*0.01)  # slightly inside
    color = 'white' if width > product_revenue.max()*0.15 else 'black'
    ha = 'right' if width > product_revenue.max()*0.15 else 'left'
    
    ax.text(
        xpos if ha=='right' else width + product_revenue.max()*0.01,
        bar.get_y() + bar.get_height()/2 + 0.1, # y_offset: 0.1
        f'{int(width)}',
        ha=ha,
        va='center',
        fontsize=10,
        # fontweight='bold',
        color=color
    )

# ax.set_yticklabels(product_revenue.index, fontweight='bold') # Make y-axis labels bold

ax.tick_params(axis='y', labelsize=10, labelrotation=0)  # adjust size/rotation if needed
# for label in ax.get_yticklabels():
    # label.set_fontweight('bold')

# for label in ax.get_xticklabels():
    # label.set_fontweight('bold')

ax.set_xlabel('Total Revenue ($)', fontweight='bold')
ax.set_title('Top 15 Products by Total Revenue', fontweight='bold', fontsize=18)

plt.tight_layout()
plt.show()

Output:

Top 15 Product by sales:

                       Product       Sales
1           Macbook Pro Laptop  8032500.00
2                       iPhone  4792900.00
3              ThinkPad Laptop  4127958.72
4                 Google Phone  3317400.00
5       27in 4K Gaming Monitor  2433147.61
6       34in Ultrawide Monitor  2352898.08
7     Apple Airpods Headphones  2345550.00
8                Flatscreen TV  1443900.00
9   Bose SoundSport Headphones  1342865.70
10            27in FHD Monitor  1131074.59
11             Vareebadd Phone   827200.00
12                20in Monitor   453818.74
13          LG Washing Machine   399600.00
14                    LG Dryer   387600.00
15    Lightning Charging Cable   346376.55

top-15-product-by-sales

5. Products Sold Together

# --- Q4: Products Sold Together ---
print("4. Calculating Products Sold Together...\n")

# Find duplicate Order IDs (items in same cart)
df_dup = df_featured[df_featured['order_id'].duplicated(keep=False)].copy()
df_dup['grouped'] = df_dup.groupby('order_id')['product'].transform(lambda x: ','.join(x))
df_dup = df_dup[['order_id', 'grouped']].drop_duplicates()

# Count pairs
count = Counter()
for row in df_dup['grouped']:
    row_list = row.split(',')
    count.update(Counter(combinations(row_list, 2)))

print("Top 10 Most Common Pairs:")
i = 1
for pair, freq in count.most_common(10):
    print(f"{i}. {pair}: {freq}")
    i+= 1

Output:

4. Calculating Products Sold Together...

Top 10 Most Common Pairs:
1. ('iPhone', 'Lightning Charging Cable'): 1002
2. ('Google Phone', 'USB-C Charging Cable'): 985
3. ('iPhone', 'Wired Headphones'): 447
4. ('Google Phone', 'Wired Headphones'): 413
5. ('Vareebadd Phone', 'USB-C Charging Cable'): 361
6. ('iPhone', 'Apple Airpods Headphones'): 360
7. ('Google Phone', 'Bose SoundSport Headphones'): 220
8. ('USB-C Charging Cable', 'Wired Headphones'): 159
9. ('Vareebadd Phone', 'Wired Headphones'): 143
10. ('Lightning Charging Cable', 'Wired Headphones'): 92

Correlation between numeric fields

# Correlation heatmap
# numerical_cols = ['quantity_ordered', 'price_each', 'sales', 'month', 'day', 'hour']
numerical_cols = ['quantity', 'price', 'sales', 'month', 'date', 'hour']
correlation_matrix = df_featured[numerical_cols].corr()

plt.figure(figsize=(10, 8))
# sns.color_palette("seagreen", as_cmap=True)
sns.color_palette("Blues", as_cmap=True)
# sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0,
sns.heatmap(correlation_matrix, annot=True, cmap='Blues', center=0,
           square=True, linewidths=0.5)
plt.title('Correlation Heatmap of Numerical Variables')
plt.tight_layout()
plt.show()

correlation-heatmap