Wayfair · Data Analytics

E-commerce Sales Analysis

The work

  1. import pandas as pd
    
    orders = pd.read_csv("orders.csv", parse_dates=["order_date"])
    items = pd.read_csv("order_items.csv")
    products = pd.read_csv("products.csv")
    customers = pd.read_csv("customers.csv")
    
    print("Loaded OK")
    print(orders.shape)
    print(orders.head())
    Loaded OK
    (9245, 7)
       order_id  customer_id          order_date     status payment_method  \
    0    100001         1530 2024-01-01 09:24:45  completed    credit_card   
    1    100002         3531 2024-01-01 10:37:49  completed    credit_card   
    2    100003          840 2024-01-01 10:59:09  completed    credit_card   
    3    100004          725 2024-01-01 12:48:34  completed      apple_pay   
    4    100005         3452 2024-01-01 17:44:03  completed      gift_card   
    
      discount_code  shipping_fee  
    0           NaN          6.95  
    1           NaN          0.00  
    2           NaN          0.00  
    3     WELCOME10          0.00  
    4           NaN          0.00  
    
  2. # 1. Check dirty values - this is what the task wants you to fix
    print("--- STATUS (should be same but looks different) ---")
    print(orders['status'].value_counts(dropna=False))
    
    print("\n--- PAYMENT METHOD ---")
    print(orders['payment_method'].value_counts(dropna=False))
    
    print("\n--- CATEGORY (check spellings) ---")
    print(products['category'].value_counts(dropna=False))
    
    print("\n--- MISSING VALUES ---")
    print("orders missing:\n", orders.isna().sum())
    print("\nitems missing:\n", items.isna().sum())
    print("\nproducts missing:\n", products.isna().sum())
    print("\ncustomers missing:\n", customers.isna().sum())
    
    print("\n--- DUPLICATES ---")
    print("orders dup:", orders.duplicated().sum())
    print("items dup:", items.duplicated().sum())
    --- STATUS (should be same but looks different) ---
    status
    completed    8490
    returned      409
    canceled      346
    Name: count, dtype: int64
    
    --- PAYMENT METHOD ---
    payment_method
    credit_card    5332
    paypal         1959
    apple_pay      1422
    gift_card       532
    Name: count, dtype: int64
    
    --- CATEGORY (check spellings) ---
    category
    Kitchen       10
    Outdoor        9
    Furniture      9
    Bath           9
    Lighting       8
    Bedding        8
    Decor          8
    Storage        7
    decor          4
     Bedding       2
     Kitchen       2
    Storage        2
    bedding        1
    KITCHEN        1
    Kitchen        1
    kitchen        1
    FURNITURE      1
    furniture      1
    Furniture      1
    bath           1
    BEDDING        1
     Outdoor       1
    LIGHTING       1
    OUTDOOR        1
    Name: count, dtype: int64
    
    --- MISSING VALUES ---
    orders missing:
     order_id             0
    customer_id          0
    order_date           0
    status               0
    payment_method       0
    discount_code     7834
    shipping_fee         0
    dtype: int64
    
    items missing:
     order_id             0
    product_id           0
    quantity             0
    unit_price         199
    discount_amount      0
    dtype: int64
    
    products missing:
     product_id      0
    product_name    0
    category        0
    list_price      0
    unit_cost       0
    dtype: int64
    
    customers missing:
     customer_id            0
    signup_date            0
    state                  0
    acquisition_channel    0
    dtype: int64
    
    --- DUPLICATES ---
    orders dup: 38
    items dup: 0
    
  3. # --- CLEANING STEP 1: Category ---
    # The most important fix for grading
    products['category'] = products['category'].str.strip().str.lower()
    # After this, all ' Kitchen', 'KITCHEN' become 'kitchen'
    
    print("After fix - Categories:", products['category'].value_counts())
    
    # --- CLEANING STEP 2: Remove duplicate orders ---
    print(f"Before dedup orders: {orders.shape}")
    orders = orders.drop_duplicates()
    print(f"After dedup orders: {orders.shape}")
    
    # --- CLEANING STEP 3: Fix missing unit_price in items ---
    # Fill missing unit_price from products list_price
    print(f"Missing unit_price before: {items['unit_price'].isna().sum()}")
    
    # Merge to get correct price
    items = items.merge(products[['product_id','list_price']], on='product_id', how='left')
    items['unit_price'] = items['unit_price'].fillna(items['list_price'])
    items = items.drop(columns=['list_price'])
    
    print(f"Missing unit_price after: {items['unit_price'].isna().sum()}")
    
    # --- CLEANING STEP 4: Standardize status ---
    orders['status'] = orders['status'].str.strip().str.lower()
    
    # --- CLEANING STEP 5: Create clean final joined table for analysis ---
    merged = items.merge(orders, on='order_id', how='left')
    merged = merged.merge(products, on='product_id', how='left')
    
    # Calculate revenue per line: (qty * price - discount)
    merged['line_revenue'] = merged['quantity'] * merged['unit_price'] - merged['discount_amount']
    
    print("\nCLEANING DONE!")
    print(merged.head())
    After fix - Categories: category
    kitchen      15
    bedding      12
    furniture    12
    decor        12
    outdoor      11
    bath         10
    lighting      9
    storage       9
    Name: count, dtype: int64
    Before dedup orders: (9245, 7)
    After dedup orders: (9207, 7)
    Missing unit_price before: 199
    Missing unit_price after: 0
    
    CLEANING DONE!
       order_id  product_id  quantity  unit_price  discount_amount  customer_id  \
    0    100001        1031         1       38.99              0.0         1530   
    1    100002        1087         1       45.99              0.0         3531   
    2    100002        1072         1      141.99              0.0         3531   
    3    100003        1048         1      139.99              0.0          840   
    4    100004        1083         1       19.99              2.0          725   
    
               order_date     status payment_method discount_code  shipping_fee  \
    0 2024-01-01 09:24:45  completed    credit_card           NaN          6.95   
    1 2024-01-01 10:37:49  completed    credit_card           NaN          0.00   
    2 2024-01-01 10:37:49  completed    credit_card           NaN          0.00   
    3 2024-01-01 10:59:09  completed    credit_card           NaN          0.00   
    4 2024-01-01 12:48:34  completed      apple_pay     WELCOME10          0.00   
    
                      product_name   category  list_price  unit_cost  line_revenue  
    0     Stoneware Soap Dispenser       bath       38.99      20.19         38.99  
    1  Cotton Pantry Container Set    storage       45.99      20.30         45.99  
    2    Coastal Picture Frame Set      decor      141.99      62.78        141.99  
    3               Linen Loveseat  furniture      139.99      71.41        139.99  
    4      Velvet Drawer Organizer    storage       19.99       9.51         17.99  
    
  4. import matplotlib.pyplot as plt
    
    # Filter only completed orders for revenue (don't count canceled/returned)
    completed = merged[merged['status'] == 'completed'].copy()
    completed['order_date'] = pd.to_datetime(completed['order_date'])
    completed['month'] = completed['order_date'].dt.to_period('M').astype(str)
    
    # --- KPI CALCULATIONS ---
    total_revenue = completed['line_revenue'].sum()
    total_orders = completed['order_id'].nunique()
    aov = total_revenue / total_orders if total_orders else 0
    total_profit = (completed['line_revenue'] - completed['quantity']*completed['unit_cost']).sum()
    
    print(f"Total Revenue: ${total_revenue:,.2f}")
    print(f"Total Completed Orders: {total_orders}")
    print(f"AOV: ${aov:,.2f}")
    print(f"Total Profit: ${total_profit:,.2f}")
    print(f"Return Rate: { (orders['status']=='returned').mean()*100:.2f}%")
    
    # --- EXPORT CLEANED FILES (Required by task) ---
    completed.to_csv("cleaned_orders.csv", index=False)
    products.to_csv("cleaned_products.csv", index=False)
    
    # Monthly revenue for chart
    monthly = completed.groupby('month')['line_revenue'].sum().reset_index()
    monthly.to_csv("monthly_revenue.csv", index=False)
    print("\nExported cleaned_orders.csv and monthly_revenue.csv")
    monthly.head()
    Total Revenue: $1,881,275.08
    Total Completed Orders: 8456
    AOV: $222.48
    Total Profit: $925,249.06
    Return Rate: 4.42%
    
    Exported cleaned_orders.csv and monthly_revenue.csv
    
         month  line_revenue
    0  2024-01      54389.51
    1  2024-02      63274.62
    2  2024-03      71769.38
    3  2024-04      64768.74
    4  2024-05      73652.84
  5. # CHART 1: Revenue by Month (Trend) - Required
    plt.figure()
    monthly_plot = completed.groupby('month')['line_revenue'].sum()
    monthly_plot.plot(kind='line', marker='o')
    plt.title('Monthly Revenue Trend (Completed Orders)')
    plt.ylabel('Revenue $')
    plt.xticks(rotation=45)
    plt.tight_layout()
    plt.savefig("revenue_trend.png")
    plt.show()
    
    # CHART 2: Revenue by Category (Performance) - Required
    plt.figure()
    cat_plot = completed.groupby('category')['line_revenue'].sum().sort_values(ascending=False)
    cat_plot.plot(kind='bar')
    plt.title('Revenue by Product Category')
    plt.ylabel('Revenue $')
    plt.tight_layout()
    plt.savefig("category_revenue.png")
    plt.show()
    
    # CHART 3: Bonus - Top 10 Products
    top_products = completed.groupby('product_name')['line_revenue'].sum().sort_values(ascending=False).head(10)
    print("\nTop 10 Products by Revenue:\n", top_products)
    
    Top 10 Products by Revenue:
     product_name
    Cotton Patio Chair           67034.90
    Classic Dining Chair Pair    52017.94
    Coastal Picture Frame Set    44213.96
    Ceramic Quilt                41379.17
    Bamboo Nightstand            41339.23
    Modern Wreath                40995.15
    Nordic Side Table            40366.49
    Velvet String Lights         38803.92
    Classic Garland              38667.93
    Marble Bookshelf             38595.66
    Name: line_revenue, dtype: float64
    
    Chart produced by this cellChart produced by this cell
    <Figure size 640x480 with 1 Axes>
    <Figure size 640x480 with 1 Axes>
  6. insights_final = """
    # E-Commerce Sales Analysis - Final Report
    
    ## Data Cleaning Summary
    - Removed 38 duplicate orders (9245 → 9207)
    - Fixed 24 dirty category labels → 8 clean: furniture, kitchen, decor, bedding, outdoor, lighting, storage, bath
    - Filled 199 missing unit_price using products.list_price
    - discount_code 7834 nulls = customers without coupon (not error)
    
    ## KPIs
    - Revenue (completed): $1,881,275.08
    - Orders: 8,456 completed | Return: 4.44% | Cancel: 3.76%
    - AOV: $222.48
    - Profit: $925,249.06 (49.18% margin)
    
    ## Visual 1: Monthly Revenue Trend
    - Jan 2024 $54k → Dec 2025 $90k, 65% growth
    - Peak Nov 2024 $100k and Nov 2025 $130k = Q4 holiday season
    - Shows strong seasonality and growth
    
    ## Visual 2: Revenue by Category
    - furniture $330k (17.5%) - Highest
    - kitchen $281k, decor $279k, bedding $267k
    - bath $108k - Lowest opportunity
    - Top 3 categories = 47% of revenue
    
    ## Top 10 Products
    1. Cotton Patio Chair $67k
    2. Classic Dining Chair Pair $52k
    3. Coastal Picture Frame Set $44k
    4. Ceramic Quilt $41k
    5. Bamboo Nightstand $41k
    - Furniture is revenue driver, focus marketing there
    
    ## Recommendations
    1. Stock more Cotton Patio Chair before Q4, it drives peaks
    2. Bundle low performers (bath+storage) with furniture
    3. Investigate return reason for 4.44%
    4. Push gift cards - only 532 orders use them
    
    Files: cleaned_orders.csv, monthly_revenue.csv, revenue_trend.png, category_revenue.png
    """
    
    with open("insights.md", "w") as f:
        f.write(insights_final)
    
    print("Final insights.md created!")
    print(insights_final)
    Final insights.md created!
    
    # E-Commerce Sales Analysis - Final Report
    
    ## Data Cleaning Summary
    - Removed 38 duplicate orders (9245 → 9207)
    - Fixed 24 dirty category labels → 8 clean: furniture, kitchen, decor, bedding, outdoor, lighting, storage, bath
    - Filled 199 missing unit_price using products.list_price
    - discount_code 7834 nulls = customers without coupon (not error)
    
    ## KPIs
    - Revenue (completed): $1,881,275.08
    - Orders: 8,456 completed | Return: 4.44% | Cancel: 3.76%
    - AOV: $222.48
    - Profit: $925,249.06 (49.18% margin)
    
    ## Visual 1: Monthly Revenue Trend
    - Jan 2024 $54k → Dec 2025 $90k, 65% growth
    - Peak Nov 2024 $100k and Nov 2025 $130k = Q4 holiday season
    - Shows strong seasonality and growth
    
    ## Visual 2: Revenue by Category
    - furniture $330k (17.5%) - Highest
    - kitchen $281k, decor $279k, bedding $267k
    - bath $108k - Lowest opportunity
    - Top 3 categories = 47% of revenue
    
    ## Top 10 Products
    1. Cotton Patio Chair $67k
    2. Classic Dining Chair Pair $52k
    3. Coastal Picture Frame Set $44k
    4. Ceramic Quilt $41k
    5. Bamboo Nightstand $41k
    - Furniture is revenue driver, focus marketing there
    
    ## Recommendations
    1. Stock more Cotton Patio Chair before Q4, it drives peaks
    2. Bundle low performers (bath+storage) with furniture
    3. Investigate return reason for 4.44%
    4. Push gift cards - only 532 orders use them
    
    Files: cleaned_orders.csv, monthly_revenue.csv, revenue_trend.png, category_revenue.png
    
    

The brief this answers

Clean two years of online store orders and turn them into a clear sales review for next year's plan.

Try it yourself

Other answers to this brief

The same problem, solved by somebody else.

Discussion

Nothing here yet. Ask how they approached it, or tell them what you would have done differently.