| name | planogram-optimization |
| description | When the user wants to optimize store planograms, shelf space allocation, or visual merchandising layout. Also use when the user mentions "planogram," "shelf space optimization," "space productivity," "category management," "shelf allocation," "fixture planning," "facings optimization," or "merchandising layout." For inventory allocation, see retail-allocation. For assortment planning, see seasonal-planning. |
Planogram Optimization
You are an expert in retail planogram optimization and space management. Your goal is to help retailers maximize sales and profitability per square foot by optimally allocating shelf space, determining product facings, and designing efficient store layouts that balance product visibility, customer experience, and operational efficiency.
Initial Assessment
Before optimizing planograms, understand:
-
Store Context
- What store format? (grocery, apparel, electronics, pharmacy)
- Store size and layout? (square footage, number of fixtures)
- Traffic patterns? (entrance location, checkout placement)
- Target customer demographics?
- Store location type? (urban, suburban, mall)
-
Category Characteristics
- What category/department needs optimization?
- Number of SKUs in category?
- Product dimensions? (height, width, depth)
- Unit movement rates? (fast vs. slow movers)
- Margin by SKU?
- Shelf life considerations? (perishable, seasonal)
-
Current Performance
- Current sales per square foot?
- Out-of-stock frequency?
- Space productivity by fixture?
- Customer satisfaction with layout?
- Labor cost for restocking?
-
Business Objectives
- Maximize revenue or profit?
- Target service level? (stock availability)
- Cross-merchandising goals?
- Brand/promotional requirements?
- Operational constraints? (restocking frequency, labor)
Planogram Optimization Framework
Space Productivity Principles
1. Space Elasticity
- Relationship between shelf space and sales
- Diminishing returns: more space doesn't always = more sales
- Optimal facings per SKU varies by product
2. Space Allocation Rules
- High-turnover items: More facings, eye-level placement
- High-margin items: Premium placement
- Impulse items: End caps, checkout
- Destination items: Can be placed in back (draws traffic)
- Complementary items: Cross-merchandising clusters
3. Shelf Height Effects
- Eye level (4-5 ft): Prime real estate, 40% of sales
- Chest level (3-4 ft): Secondary prime, 30% of sales
- Waist level (2-3 ft): Third tier, 20% of sales
- Floor level (0-2 ft): Low visibility, 10% of sales
- Above eye (5-6 ft): Overflow, occasional purchases
4. Product Adjacency
- Related products together (pasta + sauce)
- Color blocking for visual appeal
- Size progression (small to large)
- Price progression (low to high)
Space-to-Sales Analysis
Space Elasticity Modeling
import numpy as np
import pandas as pd
from scipy.optimize import minimize
import matplotlib.pyplot as plt
class SpaceElasticityAnalyzer:
"""
Analyze space elasticity - relationship between shelf space and sales
Space Elasticity = % change in sales / % change in shelf space
"""
def __init__(self, historical_data):
"""
Parameters:
- historical_data: DataFrame with space/sales experiments
columns: ['sku', 'period', 'facings', 'sales_units', 'sales_dollars']
"""
self.data = historical_data
def calculate_space_elasticity(self, sku):
"""
Calculate space elasticity coefficient
Using log-log regression: log(Sales) = a + b * log(Facings)
b is the space elasticity
"""
sku_data = self.data[self.data['sku'] == sku].copy()
if len(sku_data) < 5:
return {'error': 'Insufficient data'}
sku_data['log_facings'] = np.log(sku_data['facings'])
sku_data['log_sales'] = np.log(sku_data['sales_units'] + 1)
X = sku_data['log_facings'].values.reshape(-1, 1)
y = sku_data['log_sales'].values
sklearn.linear_model LinearRegression
model = LinearRegression()
model.fit(X, y)
elasticity = model.coef_[]
r_squared = model.score(X, y)
elasticity > :
interpretation =
elasticity > :
interpretation =
elasticity > :
interpretation =
:
interpretation =
{
: sku,
: elasticity,
: r_squared,
: interpretation,
: model
}
():
elasticity_result = .calculate_space_elasticity(sku)
elasticity_result:
model = elasticity_result[]
log_facings = np.log(target_facings)
log_sales_pred = model.predict([[log_facings]])[]
estimated_sales = np.exp(log_sales_pred) -
(, estimated_sales)
():
elasticity_result = .calculate_space_elasticity(sku)
elasticity_result:
{: }
facings_range = (, max_facings + )
results = []
facings facings_range:
estimated_sales = .estimate_sales_at_facings(sku, facings)
revenue = estimated_sales * profit_per_unit
space_cost = facings * cost_per_facing
profit = revenue - space_cost
results.append({
: facings,
: estimated_sales,
: revenue,
: space_cost,
: profit,
: profit / facings facings >
})
results_df = pd.DataFrame(results)
optimal_idx = results_df[].idxmax()
optimal = results_df.iloc[optimal_idx]
{
: optimal[],
: optimal[],
: optimal[],
: elasticity_result[],
: results_df
}
np.random.seed()
historical_data = []
sku_id (, ):
base_sales = np.random.uniform(, )
elasticity = np.random.uniform(, )
period ():
facings = np.random.randint(, )
sales = base_sales * (facings ** elasticity) + np.random.normal(, )
sales = (, sales)
historical_data.append({
: ,
: period,
: facings,
: sales,
: sales * np.random.uniform(, )
})
historical_df = pd.DataFrame(historical_data)
analyzer = SpaceElasticityAnalyzer(historical_df)
elasticity = analyzer.calculate_space_elasticity()
()
()
()
optimization = analyzer.find_optimal_facings(
sku=,
cost_per_facing=,
profit_per_unit=,
max_facings=
)
()
()
()
Planogram Optimization Models
Fixture-Level Space Allocation
class PlanogramOptimizer:
"""
Optimize product placement and facings on a fixture
Maximize sales/profit per square foot
"""
def __init__(self, fixture_config, products_data):
"""
Parameters:
- fixture_config: Dict with fixture dimensions
{'shelves': 5, 'width_inches': 48, 'depth_inches': 12}
- products_data: DataFrame with product info
columns: ['sku', 'width_inches', 'depth_inches', 'height_inches',
'weekly_sales', 'profit_per_unit', 'min_facings', 'max_facings']
"""
self.fixture = fixture_config
self.products = products_data
def calculate_space_productivity(self, allocation):
"""
Calculate sales and profit per square foot for an allocation
allocation: Dict {sku: {'shelf': shelf_num, 'facings': count}}
"""
total_sales = 0
total_profit = 0
space_used = {}
for sku, placement in allocation.items():
product = self.products[self.products['sku'] == sku].iloc[0]
facings = placement['facings']
width_per_facing = product['width_inches']
total_width = facings * width_per_facing
shelf = placement['shelf']
if shelf not in space_used:
space_used[shelf] = 0
space_used[shelf] += total_width
base_sales = product[]
elasticity = product.get(, )
adjusted_sales = base_sales * (facings ** elasticity)
total_sales += adjusted_sales
total_profit += adjusted_sales * product[]
total_space_sqft = (
.fixture[] *
.fixture[] *
.fixture[] /
)
sales_per_sqft = total_sales / total_space_sqft total_space_sqft >
profit_per_sqft = total_profit / total_space_sqft total_space_sqft >
valid =
shelf, width_used space_used.items():
width_used > .fixture[]:
valid =
{
: total_sales,
: total_profit,
: sales_per_sqft,
: profit_per_sqft,
: space_used,
: valid
}
():
.products[] = .products.apply(
row: ._calculate_priority(row, objective),
axis=
)
sorted_products = .products.sort_values(, ascending=)
allocation = {}
shelf_space_remaining = {
i: .fixture[] i (.fixture[])
}
idx, product sorted_products.iterrows():
sku = product[]
width = product[]
min_facings = product.get(, )
max_facings = product.get(, )
shelf_priority = ._get_shelf_priority_order(.fixture[])
allocated =
shelf shelf_priority:
max_facings_possible = (shelf_space_remaining[shelf] / width)
max_facings_possible >= min_facings:
facings = (max_facings, max_facings_possible)
allocation[sku] = {
: shelf,
: facings,
:
}
shelf_space_remaining[shelf] -= facings * width
allocated =
allocated:
allocation
():
objective == :
space_per_unit = product[] * product[]
(product[] * product[]) / space_per_unit
:
space_per_unit = product[] * product[]
product[] / space_per_unit
():
num_shelves <= :
((num_shelves))
middle = num_shelves //
priority = [middle]
offset (, num_shelves):
middle + offset < num_shelves:
priority.append(middle + offset)
middle - offset >= :
priority.append(middle - offset)
priority
():
allocation = .greedy_allocation(objective)
performance = .calculate_space_productivity(allocation)
allocation, performance
():
shelves = {}
sku, placement allocation.items():
shelf = placement[]
shelf shelves:
shelves[shelf] = []
product = .products[.products[] == sku].iloc[]
width = product[] * placement[]
shelves[shelf].append({
: sku,
: placement[],
: width
})
()
( * )
shelf (.fixture[] - , -, -):
(, end=)
shelf shelves:
item shelves[shelf]:
display =
(display, end=)
:
(, end=)
()
( * )
fixture_config = {
: ,
: ,
:
}
products_data = pd.DataFrame({
: [ i (, )],
: np.random.uniform(, , ),
: np.random.uniform(, , ),
: np.random.uniform(, , ),
: np.random.uniform(, , ),
: np.random.uniform(, , ),
: np.random.uniform(, , ),
: ,
: np.random.randint(, , )
})
optimizer = PlanogramOptimizer(fixture_config, products_data)
allocation, performance = optimizer.optimize_with_constraints(objective=)
()
()
()
optimizer.create_visual_planogram(allocation)
Category Management Integration
Assortment-Space Optimization
class CategorySpaceManager:
"""
Manage category-level space allocation
Decide how much space each category/subcategory gets
"""
def __init__(self, store_data):
self.store = store_data
def allocate_space_to_categories(self, categories_data,
total_space_sqft):
"""
Allocate store space across categories
Methods:
- Sales-based: Proportional to sales
- Profit-based: Proportional to profit
- Hybrid: Balance sales and profit
"""
categories_data['sales_contribution'] = (
categories_data['annual_sales'] /
categories_data['annual_sales'].sum()
)
categories_data['profit_contribution'] = (
categories_data['annual_profit'] /
categories_data['annual_profit'].sum()
)
categories_data['allocation_weight'] = (
categories_data['sales_contribution'] * 0.6 +
categories_data['profit_contribution'] * 0.4
)
categories_data['allocated_space_sqft'] = (
categories_data['allocation_weight'] * total_space_sqft
)
categories_data['current_sales_per_sqft'] = (
categories_data['annual_sales'] /
categories_data['current_space_sqft']
)
categories_data['expected_sales_per_sqft'] = (
categories_data[] /
categories_data[]
)
categories_data[[
, , ,
, , ,
]]
():
store_avg_sales_per_sqft = (
categories_data[].() /
categories_data[].()
)
recommendations = []
idx, category categories_data.iterrows():
current_productivity = category[] / category[]
ratio_to_avg = current_productivity / store_avg_sales_per_sqft
ratio_to_avg > :
recommendation =
reason =
change_pct =
ratio_to_avg < :
recommendation =
reason =
change_pct =
:
recommendation =
reason =
change_pct =
recommendations.append({
: category[],
: recommendation,
: reason,
: change_pct,
: ratio_to_avg
})
pd.DataFrame(recommendations)
categories_data = pd.DataFrame({
: [, , , , , ],
: [, , , , , ],
: [, , , , , ],
: [, , , , , ]
})
manager = CategorySpaceManager({})
allocation = manager.allocate_space_to_categories(categories_data, total_space_sqft=)
()
(allocation)
recommendations = manager.recommend_space_adjustments(categories_data)
()
(recommendations)
Cross-Merchandising & Adjacency
class CrossMerchandisingOptimizer:
"""
Optimize product adjacencies for cross-selling
Place complementary products near each other
"""
def __init__(self, products_data, affinity_matrix):
"""
Parameters:
- products_data: Product information
- affinity_matrix: Cross-purchase patterns
affinity_matrix[i][j] = likelihood customer buys j given they buy i
"""
self.products = products_data
self.affinity = affinity_matrix
def identify_product_clusters(self, min_affinity=0.3):
"""
Cluster products with high affinity
Products that are frequently bought together
"""
from sklearn.cluster import AgglomerativeClustering
clustering = AgglomerativeClustering(
n_clusters=None,
distance_threshold=1 - min_affinity,
affinity='precomputed',
linkage='average'
)
distance_matrix = 1 - self.affinity
clusters = clustering.fit_predict(distance_matrix)
self.products['cluster'] = clusters
return self.products[['sku', 'cluster']]
def score_adjacency(self, sku1, sku2):
"""
Score how beneficial it is to place two products adjacent
Higher score = more beneficial
"""
idx1 = self.products[.products[] == sku1].index[]
idx2 = .products[.products[] == sku2].index[]
affinity_1_to_2 = .affinity[idx1, idx2]
affinity_2_to_1 = .affinity[idx2, idx1]
adjacency_score = (affinity_1_to_2 + affinity_2_to_1) /
adjacency_score
():
idx = .products[.products[] == sku].index[]
affinities = .affinity[idx, :]
top_indices = np.argsort(affinities)[::-][:top_n + ]
recommendations = []
other_idx top_indices:
other_sku = .products.iloc[other_idx][]
other_sku == sku:
recommendations.append({
: other_sku,
: affinities[other_idx],
:
})
recommendations[:top_n]
n_products =
products_data = pd.DataFrame({
: [ i (, n_products + )]
})
np.random.seed()
affinity_matrix = np.random.rand(n_products, n_products) *
np.fill_diagonal(affinity_matrix, )
affinity_matrix = (affinity_matrix + affinity_matrix.T) /
optimizer = CrossMerchandisingOptimizer(products_data, affinity_matrix)
clusters = optimizer.identify_product_clusters(min_affinity=)
()
(clusters.groupby()[].apply())
recommendations = optimizer.recommend_adjacencies(, top_n=)
()
rec recommendations:
()
Tools & Libraries
Python Libraries
Optimization:
scipy.optimize: Non-linear optimization
pulp, pyomo: Linear programming for space allocation
ortools: Constraint programming for planograms
Machine Learning:
scikit-learn: Clustering for product grouping
mlxtend: Association rule mining (market basket analysis)
Visualization:
matplotlib, seaborn: Planogram visualization
plotly: Interactive layouts
PIL (Pillow): Image-based planograms
Commercial Software
Planogram Software:
- JDA/Blue Yonder Intactix: Enterprise space planning
- RELEX Solutions: Space & assortment optimization
- Galleria by Movista: Visual merchandising
- Apollo by Shelf Logic: AI-powered planograms
- SCORPION by ESL: Planogram automation
Category Management:
- Nielsen Spaceman: Space planning and optimization
- IRI ProSpace: Space productivity analytics
- Symphony RetailAI: AI-driven category management
Specialized Tools:
- SmartDraw: Basic planogram creation
- PlanoHero: Cloud planogram software
- Quant: Retail space intelligence
Common Challenges & Solutions
Challenge: Product Dimension Variability
Problem:
- Products have different sizes
- Irregular shapes don't fit neatly
- Wasted space from poor packing
Solutions:
- Modular shelf heights
- Adjustable dividers
- Product grouping by size
- Vertical stacking for small items
- Custom fixtures for odd shapes
Challenge: Frequent Assortment Changes
Problem:
- New products introduced frequently
- Seasonal rotations
- Re-planogramming is labor-intensive
Solutions:
- Flexible planogram zones
- "Hot spot" areas for new products
- Micro-category approach (easier to swap)
- Digital planograms (easy updates)
- Planogram compliance automation
Challenge: Store Format Diversity
Problem:
- Different store sizes
- Layout variations
- One planogram doesn't fit all
Solutions:
- Store clustering (A/B/C formats)
- Modular planogram approach
- Core vs. flex sections
- Automated planogram generation by store
- Local customization within guidelines
Challenge: Operational Complexity
Problem:
- Restocking difficulty
- Labor time to execute resets
- Compliance monitoring hard
Solutions:
- Operational feasibility scoring
- Minimize SKU moves during resets
- Phased implementation
- Photo compliance apps
- Planogram simplification
Challenge: Balancing Multiple Objectives
Problem:
- Maximize sales vs. profit
- Customer experience vs. efficiency
- Brand requirements vs. optimization
- Visual appeal vs. space productivity
Solutions:
- Multi-objective optimization
- Weighted scoring systems
- Constraints for brand/experience requirements
- A/B testing different approaches
- Category captain collaboration
Output Format
Planogram Optimization Report
Executive Summary:
- Category: Beverages (Soft Drinks section)
- Current performance: $450/sqft/week
- Optimized performance: $580/sqft/week (+29%)
- Fixture count: 12 fixtures (4ft sections)
- SKU count: 85 SKUs
Current vs. Optimized Performance:
| Metric | Current | Optimized | Improvement |
|---|
| Sales per sqft per week | $450 | $580 | +29% |
| Profit per sqft per week | $95 | $135 | +42% |
| Space utilization | 78% | 94% | +16 pts |
| SKU count | 85 | 72 | -13 SKUs |
| Avg facings per SKU | 3.2 | 4.5 | +41% |
Top Changes - SKU Level:
| SKU | Product | Current Facings | Optimized Facings | Change | Rationale |
|---|
| SKU001 | Coke 12pk | 4 | 8 | +4 | High sales, elastic to space |
| SKU015 | Pepsi 2L | 6 | 4 | -2 | Low elasticity, over-spaced |
| SKU023 | LaCroix variety | 2 | 6 | +4 | Growing category, undersized |
| SKU045 | Generic cola | 3 | 0 | -3 (discontinue) | Poor sales per facing |
Shelf-Level Plan:
PLANOGRAM - Soft Drinks Section (48" x 5 shelves)
============================================================
Shelf 5: [Coke12pkx8] [Pepsi12pkx6] [Sprite12pkx5]
Shelf 4: [Coke2Lx6] [Pepsi2Lx4] [DrPepper2Lx4] [Sprite2Lx4]
Shelf 3: [LaCroixX6] [BublyX5] [Perrier6pkx4]
Shelf 2: [CokeCansx4] [PepsiCansx4] [Energy6pkx5]
Shelf 1: [2LSparkling1] [2LSparkling2] [Juice4pkx3]
============================================================
Space Productivity by Fixture:
| Fixture | Current $/sqft/wk | Optimized $/sqft/wk | Improvement |
|---|
| Fixture 1 (Eye-level) | $620 | $780 | +26% |
| Fixture 2 (Eye-level) | $580 | $750 | +29% |
| Fixture 3 (Chest-level) | $480 | $610 | +27% |
| Fixture 4 (Waist-level) | $380 | $490 | +29% |
Cross-Merchandising Opportunities:
- Chips + Dips cluster: Add salsa adjacent to chips (+$8K annual sales)
- Pasta + Sauce: Consolidate for convenience (+$12K annual sales)
- Baking needs: Cluster flour, sugar, baking soda (+$6K annual sales)
Implementation Plan:
| Phase | Actions | SKUs Affected | Labor Hours | Timeline |
|---|
| Phase 1 | Adjust facings (no moves) | 25 | 8 hours | Week 1 |
| Phase 2 | Relocate high-movers to eye-level | 15 | 12 hours | Week 2 |
| Phase 3 | Discontinue poor performers | 13 | 4 hours | Week 3 |
| Phase 4 | Final adjustments & cleanup | All | 6 hours | Week 4 |
Risk & Mitigation:
| Risk | Impact | Probability | Mitigation |
|---|
| Out-of-stocks during reset | Medium | Medium | Overstock before reset, phased approach |
| Customer confusion | Low | High | Clear signage, staff briefing |
| Execution errors | Medium | Medium | Photo compliance, store visits |
Questions to Ask
If you need more context:
- What category/department needs optimization?
- How many SKUs are in the category?
- What's the current sales per square foot?
- What fixtures are you using? (shelving, gondolas, end caps)
- Do you have product dimension data?
- Do you have historical sales by SKU?
- Any space elasticity data? (testing different facings)
- What are your constraints? (brand requirements, minimum facings)
- Is this for one store or chain-wide?
Related Skills
- retail-allocation: Initial inventory allocation to stores
- retail-replenishment: Restocking strategy
- demand-forecasting: Demand forecasting by SKU/store
- inventory-optimization: Safety stock and service levels
- supply-chain-analytics: Space productivity metrics
- warehouse-slotting-optimization: Similar concepts for warehouses