mcpField
P
planogram-optimization

planogram-optimization

github.com/kishorkukrejaマーケティング・グロース8452 回インストール3 週間前に更新
リポジトリ

planogram-optimization について

小売業者向けのプラノグラム最適化スキルです。売上、利益、商品特性、店舗条件に基づく棚割りを支援し、スペース生産性、陳列位置、商品間の隣接、実施計画を扱います。

主な機能

  1. 01売上と利益に基づく棚スペース配分
  2. 02棚スペースと売上の関係分析
  3. 03フェイス数と陳列位置の計画
  4. 04関連商品の隣接配置の最適化
  5. 05変更と導入計画の作成

活用シーン

  1. 01カテゴリの平方フィート当たり売上向上
  2. 02商品回転率に応じたフェイス数の調整
  3. 03関連商品のクロスマーチャンダイジング計画
  4. 04店舗の棚替えに向けた段階的計画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill planogram-optimization

インストール後、Claude Code で/planogram-optimizationを入力して読み込まれたか確認します。

詳細

プラノグラムの最適化

小売業のプラノグラム最適化とスペース管理の専門家として、売上と平方フィートあたりの収益性を最大化できるよう支援します。棚スペースを最適に配分し、商品フェイシング数を決定し、商品の視認性、顧客体験、業務効率のバランスが取れた効率的な店舗レイアウトを設計します。

初期評価

プラノグラムを最適化する前に、次の点を把握します。

  1. 店舗の状況

    • 店舗の業態は何ですか?(食料品、アパレル、家電、薬局)
    • 店舗の規模とレイアウトはどうなっていますか?(売場面積、什器の数)
    • 顧客の動線はどうなっていますか?(入口の位置、レジの配置)
    • ターゲット顧客の属性は何ですか?
    • 店舗の立地形態は何ですか?(都市部、郊外、モール)
  2. カテゴリーの特性

    • 最適化が必要なカテゴリー/売場は何ですか?
    • カテゴリー内の SKU 数はいくつですか?
    • 商品の寸法はどのくらいですか?(高さ、幅、奥行き)
    • 商品の販売速度はどのくらいですか?(売れ筋/低回転商品)
    • SKU ごとの利益率はどのくらいですか?
    • 賞味期限や販売期間に関する考慮事項はありますか?(生鮮品、季節商品)
  3. 現在の実績

    • 現在の平方フィートあたりの売上はどのくらいですか?
    • 欠品の発生頻度はどのくらいですか?
    • 什器ごとのスペース生産性はどのくらいですか?
    • レイアウトに対する顧客満足度はどうですか?
    • 商品補充にかかる人件費はどのくらいですか?
  4. ビジネス目標

    • 売上と利益のどちらを最大化しますか?
    • 目標とするサービスレベルは何ですか?(在庫の確保状況)
    • クロスマーチャンダイジングの目標はありますか?
    • ブランドや販促に関する要件はありますか?
    • 業務上の制約はありますか?(補充頻度、人員)

プラノグラム最適化のフレームワーク

スペース生産性の原則

1. スペース弾力性

  • 棚スペースと売上の関係
  • 収穫逓減:スペースを増やしても、必ずしも売上が増えるとは限りません
  • SKU ごとに最適なフェイシング数は異なります

2. スペース配分のルール

  • 回転率の高い商品:フェイシング数を増やし、目線の高さに配置します
  • 利益率の高い商品:優先的な位置に配置します
  • 衝動買い商品:エンドキャップやレジ付近に配置します
  • 目的買い商品:奥に配置できます(顧客の来店や移動を促します)
  • 補完関係にある商品:クロスマーチャンダイジング用にまとめて配置します

3. 棚の高さによる影響

  • 目線の高さ(4~5 フィート):最優先の位置で、売上の 40%を占めます
  • 胸の高さ(3~4 フィート):次に優先度の高い位置で、売上の 30%を占めます
  • 腰の高さ(2~3 フィート):第 3 層で、売上の 20%を占めます
  • 床面(0~2 フィート):視認性が低く、売上の 10%を占めます
  • 目線より上(5~6 フィート):在庫のあふれ分や、時折購入される商品を配置します

4. 商品の隣接配置

  • 関連商品をまとめます(パスタ+ソース)
  • 見た目の魅力を高めるため、色ごとにまとめて配置します
  • サイズ順に並べます(小さいものから大きいものへ)
  • 価格順に並べます(安いものから高いものへ)

スペースと売上の分析

スペース弾力性のモデル化
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'}

        # Log transformation
        sku_data['log_facings'] = np.log(sku_data['facings'])
        sku_data['log_sales'] = np.log(sku_data['sales_units'] + 1)

        # Linear regression in log-log space
        X = sku_data['log_facings'].values.reshape(-1, 1)
        y = sku_data['log_sales'].values

        from sklearn.linear_model import LinearRegression
        model = LinearRegression()
        model.fit(X, y)

        elasticity = model.coef_[0]
        r_squared = model.score(X, y)

        # Interpretation
        if elasticity > 0.8:
            interpretation = 'High elasticity - sales very responsive to space'
        elif elasticity > 0.4:
            interpretation = 'Moderate elasticity - typical'
        elif elasticity > 0.1:
            interpretation = 'Low elasticity - limited response to space'
        else:
            interpretation = 'Very low elasticity - sales not space-dependent'

        return {
            'sku': sku,
            'elasticity': elasticity,
            'r_squared': r_squared,
            'interpretation': interpretation,
            'model': model
        }

    def estimate_sales_at_facings(self, sku, target_facings):
        """
        Estimate sales at a given number of facings

        Uses fitted elasticity model
        """

        elasticity_result = self.calculate_space_elasticity(sku)

        if 'error' in elasticity_result:
            return None

        model = elasticity_result['model']
        log_facings = np.log(target_facings)

        log_sales_pred = model.predict([[log_facings]])[0]
        estimated_sales = np.exp(log_sales_pred) - 1

        return max(0, estimated_sales)

    def find_optimal_facings(self, sku, cost_per_facing, profit_per_unit,
                            max_facings=20):
        """
        Find optimal number of facings to maximize profit

        Balance: More facings = more sales but higher space cost
        """

        elasticity_result = self.calculate_space_elasticity(sku)

        if 'error' in elasticity_result:
            return {'error': 'Cannot optimize without elasticity data'}

        facings_range = range(1, max_facings + 1)
        results = []

        for facings in facings_range:
            estimated_sales = self.estimate_sales_at_facings(sku, facings)

            # Calculate profit
            revenue = estimated_sales * profit_per_unit
            space_cost = facings * cost_per_facing

            profit = revenue - space_cost

            results.append({
                'facings': facings,
                'estimated_sales': estimated_sales,
                'revenue': revenue,
                'space_cost': space_cost,
                'profit': profit,
                'profit_per_facing': profit / facings if facings > 0 else 0
            })

        results_df = pd.DataFrame(results)

        # Find optimal
        optimal_idx = results_df['profit'].idxmax()
        optimal = results_df.iloc[optimal_idx]

        return {
            'optimal_facings': optimal['facings'],
            'expected_sales': optimal['estimated_sales'],
            'expected_profit': optimal['profit'],
            'elasticity': elasticity_result['elasticity'],
            'all_scenarios': results_df
        }

# Example usage
np.random.seed(42)

# Generate sample data - simulate space elasticity
historical_data = []

for sku_id in range(1, 6):
    # Each SKU has different elasticity
    base_sales = np.random.uniform(50, 200)
    elasticity = np.random.uniform(0.2, 0.7)

    for period in range(20):
        facings = np.random.randint(2, 15)

        # Sales = base * (facings ^ elasticity) + noise
        sales = base_sales * (facings ** elasticity) + np.random.normal(0, 10)
        sales = max(0, sales)

        historical_data.append({
            'sku': f'SKU{sku_id:03d}',
            'period': period,
            'facings': facings,
            'sales_units': sales,
            'sales_dollars': sales * np.random.uniform(3, 8)
        })

historical_df = pd.DataFrame(historical_data)

# Analyze elasticity
analyzer = SpaceElasticityAnalyzer(historical_df)
elasticity = analyzer.calculate_space_elasticity('SKU001')

print(f"Space Elasticity: {elasticity['elasticity']:.3f}")
print(f"Interpretation: {elasticity['interpretation']}")
print(f"R-squared: {elasticity['r_squared']:.3f}")

# Find optimal facings
optimization = analyzer.find_optimal_facings(
    sku='SKU001',
    cost_per_facing=2.5,  # Cost per facing per week
    profit_per_unit=4.0,   # Profit margin per unit
    max_facings=15
)

print(f"\nOptimal facings: {optimization['optimal_facings']}")
print(f"Expected weekly sales: {optimization['expected_sales']:.0f} units")
print(f"Expected weekly profit: ${optimization['expected_profit']:.2f}")

プラノグラム最適化モデル

什器単位のスペース配分
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 = {}  # Track space used per shelf

        for sku, placement in allocation.items():
            product = self.products[self.products['sku'] == sku].iloc[0]

            # Calculate space consumption
            facings = placement['facings']
            width_per_facing = product['width_inches']
            total_width = facings * width_per_facing

            shelf = placement['shelf']

            # Update space tracking
            if shelf not in space_used:
                space_used[shelf] = 0
            space_used[shelf] += total_width

            # Calculate sales (with space elasticity effect)
            base_sales = product['weekly_sales']
            elasticity = product.get('space_elasticity', 0.4)

            # Diminishing returns: sales = base * (facings ^ elasticity)
            adjusted_sales = base_sales * (facings ** elasticity)

            total_sales += adjusted_sales
            total_profit += adjusted_sales * product['profit_per_unit']

        # Calculate space utilization
        total_space_sqft = (
            self.fixture['shelves'] *
            self.fixture['width_inches'] *
            self.fixture['depth_inches'] / 144  # Convert to sqft
        )

        sales_per_sqft = total_sales / total_space_sqft if total_space_sqft > 0 else 0
        profit_per_sqft = total_profit / total_space_sqft if total_space_sqft > 0 else 0

        # Check constraints
        valid = True
        for shelf, width_used in space_used.items():
            if width_used > self.fixture['width_inches']:
                valid = False

        return {
            'total_sales': total_sales,
            'total_profit': total_profit,
            'sales_per_sqft': sales_per_sqft,
            'profit_per_sqft': profit_per_sqft,
            'space_utilization': space_used,
            'valid': valid
        }

    def greedy_allocation(self, objective='profit'):
        """
        Greedy algorithm to allocate products to fixture

        Prioritize by profit per square foot (or sales per sqft)
        """

        # Calculate priority score for each product
        self.products['priority'] = self.products.apply(
            lambda row: self._calculate_priority(row, objective),
            axis=1
        )

        # Sort by priority
        sorted_products = self.products.sort_values('priority', ascending=False)

        # Allocate
        allocation = {}
        shelf_space_remaining = {
            i: self.fixture['width_inches'] for i in range(self.fixture['shelves'])
        }

        for idx, product in sorted_products.iterrows():
            sku = product['sku']
            width = product['width_inches']
            min_facings = product.get('min_facings', 1)
            max_facings = product.get('max_facings', 10)

            # Try to allocate to best shelf position
            # Eye level (middle shelves) are most valuable
            shelf_priority = self._get_shelf_priority_order(self.fixture['shelves'])

            allocated = False

            for shelf in shelf_priority:
                max_facings_possible = int(shelf_space_remaining[shelf] / width)

                if max_facings_possible >= min_facings:
                    # Allocate optimal number of facings
                    facings = min(max_facings, max_facings_possible)

                    allocation[sku] = {
                        'shelf': shelf,
                        'facings': facings,
                        'position': 'center'  # Simplified
                    }

                    # Update remaining space
                    shelf_space_remaining[shelf] -= facings * width
                    allocated = True
                    break

            if not allocated:
                # Could not fit this product
                pass

        return allocation

    def _calculate_priority(self, product, objective):
        """Calculate priority score for product placement"""

        if objective == 'profit':
            # Profit per unit of space
            space_per_unit = product['width_inches'] * product['depth_inches']
            return (product['weekly_sales'] * product['profit_per_unit']) / space_per_unit

        else:  # sales
            space_per_unit = product['width_inches'] * product['depth_inches']
            return product['weekly_sales'] / space_per_unit

    def _get_shelf_priority_order(self, num_shelves):
        """
        Get shelf allocation priority

        Eye level (middle) is most valuable
        """

        if num_shelves <= 3:
            return list(range(num_shelves))

        # Middle shelves first
        middle = num_shelves // 2
        priority = [middle]

        # Alternate above and below middle
        for offset in range(1, num_shelves):
            if middle + offset < num_shelves:
                priority.append(middle + offset)
            if middle - offset >= 0:
                priority.append(middle - offset)

        return priority

    def optimize_with_constraints(self, objective='profit',
                                  category_constraints=None):
        """
        Optimize with business constraints

        Constraints:
        - Minimum facings per SKU
        - Maximum facings per SKU
        - Product grouping (keep related products together)
        - Brand requirements
        """

        # Use greedy as baseline
        allocation = self.greedy_allocation(objective)

        # Calculate performance
        performance = self.calculate_space_productivity(allocation)

        return allocation, performance

    def create_visual_planogram(self, allocation):
        """
        Create visual representation of planogram

        Returns ASCII art / simple visualization
        """

        # Group by shelf
        shelves = {}
        for sku, placement in allocation.items():
            shelf = placement['shelf']
            if shelf not in shelves:
                shelves[shelf] = []

            product = self.products[self.products['sku'] == sku].iloc[0]
            width = product['width_inches'] * placement['facings']

            shelves[shelf].append({
                'sku': sku,
                'facings': placement['facings'],
                'width': width
            })

        # Print planogram
        print(f"\nPLANOGRAM - {self.fixture['width_inches']}\" wide x {self.fixture['shelves']} shelves")
        print("=" * 60)

        for shelf in range(self.fixture['shelves'] - 1, -1, -1):  # Top to bottom
            print(f"Shelf {shelf + 1}:", end=" ")

            if shelf in shelves:
                for item in shelves[shelf]:
                    # Display SKU with facings
                    display = f"[{item['sku']}x{item['facings']}]"
                    print(display, end=" ")
            else:
                print("(empty)", end="")

            print()

        print("=" * 60)

# Example
fixture_config = {
    'shelves': 5,
    'width_inches': 48,
    'depth_inches': 12
}

products_data = pd.DataFrame({
    'sku': [f'SKU{i:03d}' for i in range(1, 16)],
    'width_inches': np.random.uniform(3, 8, 15),
    'depth_inches': np.random.uniform(4, 10, 15),
    'height_inches': np.random.uniform(6, 12, 15),
    'weekly_sales': np.random.uniform(10, 100, 15),
    'profit_per_unit': np.random.uniform(2, 8, 15),
    'space_elasticity': np.random.uniform(0.3, 0.6, 15),
    'min_facings': 1,
    'max_facings': np.random.randint(4, 12, 15)
})

optimizer = PlanogramOptimizer(fixture_config, products_data)

# Optimize
allocation, performance = optimizer.optimize_with_constraints(objective='profit')

print(f"Total weekly sales: ${performance['total_sales']:.0f}")
print(f"Total weekly profit: ${performance['total_profit']:.0f}")
print(f"Profit per sqft: ${performance['profit_per_sqft']:.2f}")

# Visualize
optimizer.create_visual_planogram(allocation)

カテゴリー管理との統合

品揃えとスペースの最適化
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
        """

        # Calculate each category's contribution
        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()
        )

        # Hybrid allocation (60% sales, 40% profit)
        categories_data['allocation_weight'] = (
            categories_data['sales_contribution'] * 0.6 +
            categories_data['profit_contribution'] * 0.4
        )

        # Allocate space
        categories_data['allocated_space_sqft'] = (
            categories_data['allocation_weight'] * total_space_sqft
        )

        # Calculate expected productivity
        categories_data['current_sales_per_sqft'] = (
            categories_data['annual_sales'] /
            categories_data['current_space_sqft']
        )

        categories_data['expected_sales_per_sqft'] = (
            categories_data['annual_sales'] /
            categories_data['allocated_space_sqft']
        )

        return categories_data[[
            'category', 'current_space_sqft', 'allocated_space_sqft',
            'annual_sales', 'annual_profit', 'current_sales_per_sqft',
            'expected_sales_per_sqft'
        ]]

    def recommend_space_adjustments(self, categories_data):
        """
        Identify categories that need more or less space

        Based on:
        - Sales per sqft vs. store average
        - Growth trends
        - Profit margins
        """

        store_avg_sales_per_sqft = (
            categories_data['annual_sales'].sum() /
            categories_data['current_space_sqft'].sum()
        )

        recommendations = []

        for idx, category in categories_data.iterrows():
            current_productivity = category['annual_sales'] / category['current_space_sqft']
            ratio_to_avg = current_productivity / store_avg_sales_per_sqft

            if ratio_to_avg > 1.3:
                recommendation = 'Expand space'
                reason = f"High productivity ({ratio_to_avg:.1f}x store average)"
                change_pct = '+15 to +25%'

            elif ratio_to_avg < 0.7:
                recommendation = 'Reduce space'
                reason = f"Low productivity ({ratio_to_avg:.1f}x store average)"
                change_pct = '-15 to -25%'

            else:
                recommendation = 'Maintain space'
                reason = 'Productivity in line with average'
                change_pct = '±5%'

            recommendations.append({
                'category': category['category'],
                'recommendation': recommendation,
                'reason': reason,
                'suggested_change': change_pct,
                'productivity_ratio': ratio_to_avg
            })

        return pd.DataFrame(recommendations)

# Example
categories_data = pd.DataFrame({
    'category': ['Dairy', 'Bakery', 'Produce', 'Meat', 'Frozen', 'Beverages'],
    'current_space_sqft': [800, 600, 1200, 900, 1500, 2000],
    'annual_sales': [1_200_000, 800_000, 1_500_000, 1_800_000, 1_400_000, 2_200_000],
    'annual_profit': [180_000, 240_000, 300_000, 360_000, 210_000, 330_000]
})

manager = CategorySpaceManager({})

# Allocate space
allocation = manager.allocate_space_to_categories(categories_data, total_space_sqft=7000)
print("Category Space Allocation:")
print(allocation)

# Recommendations
recommendations = manager.recommend_space_adjustments(categories_data)
print("\nSpace Adjustment Recommendations:")
print(recommendations)

クロスマーチャンダイジングと隣接配置

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

        # Use affinity matrix for clustering
        clustering = AgglomerativeClustering(
            n_clusters=None,
            distance_threshold=1 - min_affinity,
            affinity='precomputed',
            linkage='average'
        )

        # Convert affinity to distance
        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[self.products['sku'] == sku1].index[0]
        idx2 = self.products[self.products['sku'] == sku2].index[0]

        # Bidirectional affinity
        affinity_1_to_2 = self.affinity[idx1, idx2]
        affinity_2_to_1 = self.affinity[idx2, idx1]

        # Combined score
        adjacency_score = (affinity_1_to_2 + affinity_2_to_1) / 2

        return adjacency_score

    def recommend_adjacencies(self, sku, top_n=5):
        """
        Recommend products to place next to a given SKU

        Based on cross-purchase affinity
        """

        idx = self.products[self.products['sku'] == sku].index[0]

        # Get affinities for this product
        affinities = self.affinity[idx, :]

        # Sort and get top N
        top_indices = np.argsort(affinities)[::-1][:top_n + 1]  # +1 to exclude self

        recommendations = []
        for other_idx in top_indices:
            other_sku = self.products.iloc[other_idx]['sku']

            if other_sku == sku:
                continue  # Skip self

            recommendations.append({
                'sku': other_sku,
                'affinity_score': affinities[other_idx],
                'rationale': 'Frequently purchased together'
            })

        return recommendations[:top_n]

# Example
n_products = 20
products_data = pd.DataFrame({
    'sku': [f'SKU{i:03d}' for i in range(1, n_products + 1)]
})

# Generate sample affinity matrix
# (in practice, this comes from transaction data)
np.random.seed(42)
affinity_matrix = np.random.rand(n_products, n_products) * 0.5
np.fill_diagonal(affinity_matrix, 1.0)  # Product always purchased with itself

# Make it symmetric (for simplicity)
affinity_matrix = (affinity_matrix + affinity_matrix.T) / 2

optimizer = CrossMerchandisingOptimizer(products_data, affinity_matrix)

# Identify clusters
clusters = optimizer.identify_product_clusters(min_affinity=0.3)
print("Product Clusters:")
print(clusters.groupby('cluster')['sku'].apply(list))

# Get adjacency recommendations
recommendations = optimizer.recommend_adjacencies('SKU001', top_n=5)
print(f"\nRecommended adjacencies for SKU001:")
for rec in recommendations:
    print(f"  {rec['sku']}: {rec['affinity_score']:.2f}")

ツールとライブラリ

Python ライブラリ

最適化:

  • scipy.optimize:非線形最適化
  • pulp、pyomo:スペース配分のための線形計画法
  • ortools:プラノグラムのための制約プログラミング

機械学習:

  • scikit-learn:商品グループ化のためのクラスタリング
  • mlxtend:アソシエーションルールマイニング(マーケットバスケット分析)

可視化:

  • matplotlib、seaborn:プラノグラムの可視化
  • plotly:インタラクティブなレイアウト
  • PIL(Pillow):画像ベースのプラノグラム
商用ソフトウェア

プラノグラムソフトウェア:

  • JDA/Blue Yonder Intactix:エンタープライズ向けスペースプランニング
  • RELEX Solutions:スペースと品揃えの最適化
  • Galleria by Movista:ビジュアルマーチャンダイジング
  • Apollo by Shelf Logic:AI を活用したプラノグラム
  • SCORPION by ESL:プラノグラムの自動化

カテゴリー管理:

  • Nielsen Spaceman:スペースプランニングと最適化
  • IRI ProSpace:スペース生産性分析
  • Symphony RetailAI:AI を活用したカテゴリー管理

専門ツール:

  • SmartDraw:基本的なプラノグラム作成
  • PlanoHero:クラウド型プラノグラムソフトウェア
  • Quant:小売スペースのインテリジェンス

よくある課題と解決策

課題:商品サイズのばらつき

問題:

  • 商品によってサイズが異なります
  • 形状が不規則で、きれいに収まりません
  • 陳列の仕方が適切でないと、スペースが無駄になります

解決策:

  • 棚の高さをモジュール式にする
  • 仕切りを調整可能にする
  • サイズ別に商品をグループ化する
  • 小さな商品を縦に積み重ねる
  • 形状が特殊な商品向けに専用什器を用意する
課題:品ぞろえの頻繁な変更

問題:

  • 新商品が頻繁に投入されます
  • 季節ごとに商品が入れ替わります
  • プラノグラムの再作成に手間がかかります

解決策:

  • 柔軟に変更できるプラノグラムのゾーンを設ける
  • 新商品向けの「注目エリア」を設ける
  • マイクロカテゴリー方式を採用する(入れ替えが容易です)
  • デジタルプラノグラムを活用する(簡単に更新できます)
  • プラノグラム遵守状況の確認を自動化する
課題:店舗形態の多様性

問題:

  • 店舗の規模が異なります
  • レイアウトに違いがあります
  • 1つのプラノグラムですべての店舗に対応できません

解決策:

  • 店舗をクラスター化する(A/B/C形式)
  • モジュール式のプラノグラムを採用する
  • 基本区画と柔軟に変更できる区画を設ける
  • 店舗ごとのプラノグラム生成を自動化する
  • ガイドラインの範囲内で地域に合わせてカスタマイズする
課題:運用の複雑さ

問題:

  • 商品の補充が難しいです
  • レイアウト変更の実施に作業時間がかかります
  • 遵守状況の監視が困難です

解決策:

  • 運用実現性を評価する
  • レイアウト変更時の商品移動を最小限に抑える
  • 段階的に導入する
  • 写真による遵守状況確認アプリを活用する
  • プラノグラムを簡素化する
課題:複数の目標のバランスを取る

問題:

  • 売上の最大化と利益の最大化
  • 顧客体験と効率性
  • ブランド要件と最適化
  • 見た目の魅力とスペースの生産性

解決策:

  • 多目的最適化を行う
  • 重み付けスコアリングシステムを導入する
  • ブランドや顧客体験に関する要件を制約条件にする
  • 異なるアプローチを A/B テストする
  • カテゴリーキャプテンと連携する

出力形式

プラノグラム最適化レポート

エグゼクティブサマリー:

  • カテゴリー:飲料(清涼飲料セクション)
  • 現在の実績:週あたり1平方フィートあたり450ドル
  • 最適化後の実績:週あたり1平方フィートあたり580ドル(+29%)
  • 棚数:12台(4フィート区画)
  • SKU数:85 SKU

最適化前と最適化後の実績:

指標現在最適化後改善
週あたり1平方フィートあたりの売上450ドル580ドル+29%
週あたり1平方フィートあたりの利益95ドル135ドル+42%
スペース活用率78%94%+16ポイント
SKU数8572-13 SKU
SKUあたりの平均フェイス数3.24.5+41%

主な変更点 - SKUレベル:

SKU商品現在のフェイス数最適化後のフェイス数変更理由
SKU001Coke 12pk48+4売上が高く、スペースの影響を受けやすい
SKU015Pepsi 2L64-2弾力性が低く、スペースを取りすぎている
SKU023LaCroix variety26+4カテゴリーが成長しており、スペースが不足している
SKU045Generic cola30-3(取扱中止)フェイスあたりの売上が低い

棚レベルのプラン:

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]
============================================================

什器別のスペース生産性:

什器現在の週あたり$/平方フィート最適化後の週あたり$/平方フィート改善
什器1(目線の高さ)620ドル780ドル+26%
什器2(目線の高さ)580ドル750ドル+29%
什器3(胸の高さ)480ドル610ドル+27%
什器4(腰の高さ)380ドル490ドル+29%

クロスマーチャンダイジングの機会:

  1. チップス+ディップのクラスター:チップスの隣にサルサを追加(年間売上8,000ドル増)
  2. パスタ+ソース:利便性を高めるためにまとめて陳列(年間売上12,000ドル増)
  3. 製菓材料:小麦粉、砂糖、重曹をまとめて陳列(年間売上6,000ドル増)

導入計画:

フェーズ作業対象SKU作業時間期間
フェーズ1フェイス数を調整(移動なし)258時間1週目
フェーズ2売れ行きの良い商品を目線の高さに移動1512時間2週目
フェーズ3販売実績の低い商品を取扱中止134時間3週目
フェーズ4最終調整と片付けすべて6時間4週目

リスクと軽減策:

リスク影響発生確率軽減策
棚替え中の欠品中中棚替え前に在庫を積み増し、段階的に実施
顧客の混乱低高わかりやすい表示、スタッフへの事前説明
作業ミス中中写真による遵守確認、店舗訪問

確認事項

さらに詳しい情報が必要な場合:

  1. 最適化が必要なカテゴリー/部門は何ですか?
  2. そのカテゴリーにはSKUがいくつありますか?
  3. 現在の1平方フィートあたりの売上はいくらですか?
  4. どのような什器を使用していますか?(棚、ゴンドラ、エンドキャップ)
  5. 商品の寸法データはありますか?
  6. SKU別の過去の売上データはありますか?
  7. スペース弾力性のデータはありますか?(フェイス数を変えたテスト)
  8. 制約はありますか?(ブランド要件、最低フェイス数)
  9. 対象は1店舗ですか、それともチェーン全体ですか?

関連スキル

  • retail-allocation:店舗への初期在庫配分
  • retail-replenishment:補充戦略
  • demand-forecasting:SKU/店舗別の需要予測
  • inventory-optimization:安全在庫とサービスレベル
  • supply-chain-analytics:スペース生産性の指標
  • warehouse-slotting-optimization:倉庫における類似の概念

コメント