Slotting Architecture · 6 min read

Implementing Complementary Product Grouping for High-Velocity Picking

When pickers repeatedly traverse disjointed aisles to fulfill multi-SKU orders, travel time inflates and throughput degrades. Grouping complementary products for faster picking requires moving beyond heuristic placement to algorithmic affinity mapping constrained by physical capacity, velocity tiers, and dynamic inventory states. The following implementation guide details how to compute co-pick affinity, apply hard constraints, and deploy a production-ready slotting recommendation engine.

Algorithmic Foundation & Velocity Integration

Complementary grouping fails when affinity scores ignore pick frequency or physical limits. A robust system must weight co-occurrence by order velocity, then map high-affinity pairs to adjacent or shared locations. This process sits directly within the broader framework of Location Assignment & ABC Classification Algorithms, where velocity tiers dictate proximity to packing stations and cross-dock zones.

The core calculation relies on a velocity-adjusted lift metric: Lift(A,B) = P(A∩B) / (P(A) * P(B))

Probabilities are derived from historical pick tickets, but raw lift must be normalized against order frequency to prevent statistical noise from low-volume SKUs. High lift indicates strong complementary demand, yet placement decisions cannot rely on affinity alone. You must filter recommendations through ABC velocity bands, weight/volume constraints, and bin capacity limits before generating physical adjacency instructions.

Constraint Modeling & Physical Limits

Affinity alone cannot dictate placement. Heavy items cannot share lightweight shelving, and fast-moving SKUs require forward pick zones with strict cubic capacity limits. Integrating Family & Affinity Grouping ensures that algorithmic recommendations respect real-world warehouse geometry and operational safety standards.

Effective constraint modeling requires three layers:

  1. Static Constraints: Weight limits per shelf, hazardous material segregation, and temperature zones.
  2. Dynamic Constraints: Real-time bin occupancy, seasonal velocity shifts, and cross-dock staging availability.
  3. Fallback Assignment Chains: When primary affinity zones saturate, the system must cascade recommendations to secondary zones without breaking pick-path continuity.

Threshold optimization for re-slotting prevents excessive labor churn. Set minimum lift thresholds (typically ≥ 1.5) and velocity decay windows (e.g., 30-day rolling averages) to trigger slotting updates only when statistically significant.

Production-Ready Python Implementation

The following script ingests order history, computes velocity-weighted affinity, applies physical constraints, and outputs a prioritized grouping matrix. It uses standard pandas and numpy and is structured for direct integration into WMS data pipelines. For detailed API references on matrix operations, consult the official pandas documentation.

import pandas as pd
import numpy as np
from itertools import combinations
import warnings
warnings.filterwarnings('ignore')

def compute_affinity_groupings(order_history: pd.DataFrame,
                               sku_master: pd.DataFrame,
                               config: dict) -> pd.DataFrame:
    """
    Computes velocity-weighted affinity and applies slotting constraints.

    Args:
        order_history: DataFrame with columns ['order_id', 'sku_id', 'qty_picked']
        sku_master: DataFrame with columns ['sku_id', 'velocity_class', 'weight_kg', 'volume_cm3']
        config: Dict with keys: 'min_lift', 'max_shared_weight_kg', 'max_shared_volume_cm3'

    Returns:
        DataFrame with SKU pairs, affinity scores, constraint status, and recommended zone.
    """
    if order_history.empty or sku_master.empty:
        return pd.DataFrame()

    # 1. Build co-occurrence matrix from order history
    order_sku = order_history.pivot_table(index='order_id', columns='sku_id',
                                          values='qty_picked', aggfunc='count', fill_value=0)
    sku_list = order_sku.columns

    # 2. Calculate probability and lift
    total_orders = order_history['order_id'].nunique()
    sku_probs = order_sku.sum() / total_orders

    # Pre-allocate lift matrix
    lift_matrix = pd.DataFrame(0.0, index=sku_list, columns=sku_list)

    for sku_a, sku_b in combinations(sku_list, 2):
        p_a = sku_probs[sku_a]
        p_b = sku_probs[sku_b]
        p_both = (order_sku[sku_a] & order_sku[sku_b]).sum() / total_orders

        if p_a > 0 and p_b > 0:
            lift_val = p_both / (p_a * p_b)
            lift_matrix.loc[sku_a, sku_b] = lift_val
            lift_matrix.loc[sku_b, sku_a] = lift_val

    # 3. Apply velocity weighting (A=1.5, B=1.0, C=0.5)
    velocity_weights = {'A': 1.5, 'B': 1.0, 'C': 0.5}
    sku_master = sku_master.set_index('sku_id')

    weighted_lift = lift_matrix.copy()
    for sku in sku_list:
        v_class = sku_master.loc[sku, 'velocity_class']
        weight = velocity_weights.get(v_class, 1.0)
        weighted_lift[sku] *= weight

    # 4. Filter by threshold and build constraint matrix
    min_lift = config.get('min_lift', 1.5)
    max_weight = config.get('max_shared_weight_kg', 50.0)
    max_volume = config.get('max_shared_volume_cm3', 100000.0)

    mask = weighted_lift >= min_lift
    valid_pairs = np.argwhere(mask.values)

    results = []
    for i, j in valid_pairs:
        sku_a, sku_b = sku_list[i], sku_list[j]
        if sku_a >= sku_b:  # Avoid duplicates
            continue

        w_a = sku_master.loc[sku_a, 'weight_kg']
        w_b = sku_master.loc[sku_b, 'weight_kg']
        v_a = sku_master.loc[sku_a, 'volume_cm3']
        v_b = sku_master.loc[sku_b, 'volume_cm3']

        weight_ok = (w_a + w_b) <= max_weight
        volume_ok = (v_a + v_b) <= max_volume

        # Determine zone based on highest velocity class
        v_classes = [sku_master.loc[sku_a, 'velocity_class'], sku_master.loc[sku_b, 'velocity_class']]
        primary_zone = 'A' if 'A' in v_classes else ('B' if 'B' in v_classes else 'C')

        results.append({
            'sku_id_1': sku_a,
            'sku_id_2': sku_b,
            'velocity_adjusted_lift': weighted_lift.iloc[i, j],
            'combined_weight_kg': w_a + w_b,
            'combined_volume_cm3': v_a + v_b,
            'constraint_passed': bool(weight_ok and volume_ok),
            'recommended_zone': primary_zone
        })

    return pd.DataFrame(results).sort_values('velocity_adjusted_lift', ascending=False)

Deployment, Threshold Tuning & Edge Cases

Deploying affinity grouping requires phased rollout and continuous validation. Start by running the engine in shadow mode against historical pick paths to measure theoretical travel reduction before pushing recommendations to the WMS location management module.

Threshold Optimization for Re-slotting

Static thresholds cause slotting thrash. Implement rolling 30-day velocity windows and set a minimum order frequency (e.g., ≥ 15 picks/month) before a pair qualifies for adjacency. Use a hysteresis buffer: only trigger re-slotting when lift exceeds the threshold for two consecutive evaluation cycles.

Handling Data Sparsity & Cold Starts

New SKUs lack co-pick history. Fallback to category-level affinity or supplier-based grouping until sufficient transactional data accumulates. For seasonal assortments, apply decay factors to older lift scores to prevent legacy demand from distorting current placement.

Constraint Conflict Resolution

When affinity recommends pairing items that violate weight or volume limits, the engine must invoke a fallback assignment chain. Prioritize:

  1. Adjacent bin placement within the same velocity zone
  2. Split-pair routing optimization (direct pickers to sequential locations)
  3. Manual override flags for high-value but physically incompatible items

Monitoring & Validation

Track three KPIs post-deployment:

  • Travel Distance Reduction: Measured via WMS pathing logs
  • Pick Accuracy Impact: Ensure grouping doesn’t increase mis-pick rates due to visual similarity
  • Slotting Labor Cost: Balance affinity gains against physical relocation labor

For standardized metrics on warehouse performance and slotting efficiency, reference the ASCM Supply Chain Operations Reference framework.

Conclusion

Complementary product grouping transforms disjointed pick paths into optimized, velocity-aligned workflows. By anchoring affinity calculations to historical co-occurrence, weighting by ABC velocity, and enforcing hard physical constraints, logistics teams can achieve measurable travel reduction without compromising bin integrity or operational safety. Integrate the provided Python engine into your data pipeline, tune thresholds iteratively, and validate against real-world pick telemetry to sustain long-term throughput gains.