Assortimentsoptimalisatie is een cruciaal proces in de detailhandel waarbij de ideale mix van producten wordt samengesteld om aan de vraag van de consument te voldoen, rekening houdend met de vele logistieke beperkingen. De retailers moeten ervoor zorgen dat ze de juiste producten in de juiste hoeveelheden op het juiste moment aanbieden. Door gebruik te maken van data en consumenteninzichten kunnen retailers weloverwogen beslissingen nemen over welke artikelen ze in voorraad moeten nemen, hoe ze de voorraad moeten beheren en welke producten ze prioriteit moeten geven op basis van klantvoorkeuren, seizoensgebonden trends en verkooppatronen.
Voor detailhandelsbedrijven is assortimentsoptimalisatie essentieel om een balans te vinden tussen variëteit en efficiëntie. Te weinig keuze kan klanten wegjagen, terwijl te veel keuze kan leiden tot verwarring, overtollige voorraad en lagere winstmarges. Het optimaliseren van het productassortiment helpt bedrijven de klanttevredenheid te verhogen door ervoor te zorgen dat populaire artikelen beschikbaar zijn, terwijl slecht presterende producten die kostbare schapruimte in beslag nemen, worden geëlimineerd.
Keuzemodellering is een efficiënte manier om assortimentsoptimalisatie te benaderen, omdat het een data kader biedt om de voorkeuren van klanten te begrijpen en te voorspellen hoe ze zullen kiezen tussen verschillende producten. Door verschillende factoren te analyseren, zoals prijsgevoeligheid, productkenmerken en merkentrouw, helpt keuzemodellering retailers te bepalen welke producten het meest waarschijnlijk aan de vraag van de klant zullen voldoen.
Uiteindelijk stelt keuzemodellering retailers in staat om de juiste mix van producten aan te bieden, assortimenten af te stemmen op specifieke klantsegmenten en kan schapruimte worden geoptimaliseerd om de winstgevendheid of zelfs de prijs van artikelen te verhogen.
Als je nog nooit van keuzemodellen hebt gehoord, kun je ons artikel lezen waarin de belangrijkste concepten met voorbeelden worden uitgelegd. In dit artikel richten we ons vooral op hoe discrete keuzemodellen kunnen worden gebruikt om een assortiment producten te optimaliseren. We geven codevoorbeelden gebaseerd op de choice-learn bibliotheek, die is ontworpen om data te helpen bij dergelijke use cases.
De meegeleverde code maakt gebruik van het choice-learn Python-pakket en kan worden gevonden in een notebook hier.
Opzetten: Python en Choice-Learn installeren
In dit artikel geven we stukjes code bij de uitleg. De code maakt gebruik van de bibliotheek Choice-Learn, die efficiënte hulpmiddelen biedt voor keuzemodellering en verschillende toepassingen, zoals assortimentsoptimalisatie of prijs. Choice-Learn is beschikbaar via PyPI, je kunt het eenvoudig verkrijgen met
De dataset: kassabonnen
We gebruiken de TaFeng kruidenierswinkel dataset. Je kunt deze downloaden van Kaggle en openen in je Python-omgeving met choice-learn:
print(tafeng_df.head())
De dataset bestaat uit meer dan 800.000 individuele aankopen in een Chinese kruidenierswinkel. Voor elke aankoop worden verschillende gegevens verstrekt, waaronder het gekochte artikel (PRODUCT_ID), de prijs waarvoor het werd verkocht (SALES_PRICE) en de leeftijdsgroep van de klant (AGE_GROUP).
Je kunt zien dat er veel verschillende artikelen worden aangeboden en dat sommige daarvan zelden worden verkocht. Om de logistiek te stroomlijnen, kan de detailhandelaar ervoor kiezen om het aantal producten dat hij aanbiedt te verminderen. Het doel in dit geval is om de optimale subset van te verkopen artikelen te identificeren.
Om dit te bereiken richten we ons op de best verkopende artikelen, omdat het waarschijnlijker is dat deze opnieuw worden gekocht en een cruciale rol zullen spelen bij het vormgeven van een efficiënter en winstgevender assortiment. Merk op dat we dit vooral doen om het voorbeeld te vereenvoudigen en dat alle artikelen behouden zouden kunnen worden.
tafeng_df = tafeng_df.loc[
tafeng_df.PRODUCT_ID.isin(tafeng_df.PRODUCT_ID.value_counts().index[:20])
].reset_index(drop=True)
tafeng_df = tafeng_df.loc[
tafeng_df.LEEFTIJD_GROEP.isin(["25-29", "40-44", "45-49", ">65", "30-34", "35-39", "50-54", "55-59", "60-64"] )
].reset_index(drop=True)
Laten we ook de leeftijdscategorieën coderen met één hete waarde per tien jaar:
tafeng_df["twenties"]= tafeng_df.apply(lambda rij: 1 if rij["AGE_GROUP"]== "25-29" anders 0, as=1)
tafeng_df["dertigers"]= tafeng_df.apply(
lambda rij: 1 als rij["LEEFTIJDGROEP"] in (["30-34", "35-39"]) anders 0, as=1
)
tafeng_df["veertigers"]= tafeng_df.apply(
lambda rij: 1 als rij["LEEFTIJDGROEP"] in (["40-44", "45-49"]) anders 0, as=1
)
tafeng_df["vijftigers"]= tafeng_df.apply(
lambda rij: 1 als rij["LEEFTIJDGROEP"] in (["50-54", "55-59"]) anders 0, as=1
)
tafeng_df["sixties_and_above"]= tafeng_df.apply(
lambda rij: 1 als rij["LEEFTIJDGROEP"] in (["60-64", ">65"]) anders 0, as=1
)
Nu onze data klaar zijn, moeten we een ChoiceDataset maken, het data handler object in choice-learn. Hiervoor moeten we de kenmerken specificeren die de context beschrijven waarin een aankoop wordt gedaan:
- Klantkenmerken (gedeelde kenmerken): de leeftijdscategorie
- Productkenmerken (producteigenschappen): de prijs van het product
Een belangrijk aspect van keuzemodellering is dat we de kenmerken van alle beschikbare artikelen nodig hebben op het moment van een aankoop, niet alleen het gekozen artikel. Hierdoor kunnen we analyseren hoe de prijzen van verschillende producten de beslissing van de klant beïnvloeden. Omdat deze informatie niet direct beschikbaar is in de dataset, nemen we aan dat bij elke aankoop de prijzen van de andere artikelen hetzelfde blijven als bij de vorige verkoop.
id_to_index = {}
for i, product_id in enumerate(np.sort(tafeng_df.PRODUCT_ID.unique())):
id_to_index[product_id] = i
# Initialize the items price
prices = [[0] for _ in range(len(id_to_index))] for k, v in id_to_index.items():
prices[v][0] = tafeng_df.loc[tafeng_df.PRODUCT_ID == k].SALES_PRICE.to_numpy()[0] # Create the arrays that will constitute the ChoiceDataset
shared_features = [] items_features = [] choices = [] # For each bought item, we save:
# – the age representation (one-hot) of the customer
# – the price of all sold items
for i, row in tafeng_df.iterrows():
item_index = id_to_index[row.PRODUCT_ID] prices[item_index][0] = row.SALES_PRICE
shared_features.append(
row[[“twenties”, “thirties”, “forties”, “fifties”, “sixties_and_above”]].to_numpy()
)
items_features.append(prices)
choices.append(item_index)
Nu we al onze informatie hebben, kunnen we de ChoiceDataset maken:
dataset = KeuzeDataset(
gedeelde_kenmerken_bij_keuze=gedeelde_kenmerken,
shared_features_by_choice_names=['twenties', 'dertigers', 'veertigers', 'vijftigers', 'zestigers_en_boven'],
items_features_by_choice=items_features,
items_features_by_choice_names=["SALES_PRICE"],
keuzes=keuzes
)
Het keuzemodel definiëren en schatten
We zullen een keuzemodel ontwikkelen en schatten dat de waarschijnlijkheid voorspelt dat een klant een specifiek artikel kiest uit een volledig assortiment van vergelijkbare producten. Op basis van de beschikbare dataset definiëren we de volgende nutsfunctie voor een artikel i dat door een klant j wordt overwogen :
Deze functie vertegenwoordigt het nut (of de voldoening) die een klant haalt uit het kiezen van een bepaald artikel, beïnvloed door zowel de leeftijd van de klant als de prijs van het artikel.
Voor meer details over hoe we een nutsfunctie formuleren, verwijzen we naar onze eerste post. Merk op dat een ander logisch - maar niet gepresenteerd om het eenvoudig te houden - model zou kunnen zijn om één prijsgevoeligheid per leeftijdscategorie te schatten.
Hier is de code om een dergelijk model te schatten met choice-learn:
model.add_coefficients(
coëfficiënt_naam=leeftijd_categorie, kenmerk_naam=leeftijd_categorie, items_indexen=lijst(bereik(20))
)
coëfficiënt_naam="prijs", kenmerk_naam="VERKOOPPRIJS",items_indexen=lijst(bereik(20))
)
U kunt controleren of het model goed past op de dataset:
plt.plot(hist["train_loss"])
plt.xlabel("Tijdperk")
plt.ylabel("Negatieve log waarschijnlijkheid")
plt.show(
Het optimale assortiment vinden
Met de aankoopkansen in de hand kunnen we nu de gemiddelde opbrengst per klant van assortiment A schatten met behulp van de formule:
Om het assortiment te vinden dat de opbrengst maximaliseert, zouden we alle mogelijke combinaties kunnen evalueren en degene met de hoogste gemiddelde opbrengst selecteren. Een efficiëntere aanpak is echter Lineair Programmeren (LP). Hier zullen we ons richten op het gebruik van de choice-learn implementatie van de assortimentsoptimalisator.
Het is belangrijk om onderscheid te maken tussen het maximaliseren van inkomsten en het maximaliseren van winstmarges. Terwijl inkomsten belangrijk zijn, houden winstmarges rekening met de kosten die gepaard gaan met elk product. Afhankelijk van je doel wil je misschien optimaliseren voor winst in plaats van pure inkomsten.
Om het assortiment te optimaliseren, hebben we een aantal belangrijke inputs nodig:
- Het gewicht dat we aan elke leeftijdscategorie willen geven, laten we uitgaan van hun klantenaandeel
- Het nut van elk item (berekend door ons keuzemodel) voor elke leeftijdscategorie
- De waarde die moet worden geoptimaliseerd voor elk item (in dit geval inkomsten)
- De grootte van het assortiment (bijvoorbeeld 12 artikelen)
Dit is hoe het werkt met behulp van choice-learn:
uit choice_learn.toolbox.assortment_optimizer importeer LatentClassAssortmentOptimizer
# Prijs van elk item
toekomstige_prijzen = np.stack([items_features[-1]]*5, as=0)
leeftijd_categorie = np.eye(5).astype("float32")
# Bereken het nut van elk item op basis van de prijs en elke leeftijdscategorie
voorspelde_utilities = model.compute_batch_utility(shared_features_by_choice=leeftijd_category,
items_features_by_choice=toekomstige_prijzen,
beschikbare_items_bij_keuze=np.ones((5, 20)),
keuzes=geen
)
leeftijd_categorie_gewichten = np.som(gedeelde_kenmerken, as=0) / len(gedeelde_kenmerken)
opt = LatentClassAssortmentOptimizer(
solver="or-tools", # te gebruiken solver, ofwel "or-tools" of "gurobi" (als je een licentie hebt)
class_weights=age_category_weights, # Gewichten van elke klasse
class_utilities=np.exp(predicted_utilities), # nutsvoorzieningen in de vorm (n_classes, n_items)
itemwise_values=future_prices[0][:, 0], # Waarden om te optimaliseren voor elk item, hier prijs die wordt gebruikt om omzet te berekenen
assortment_size=12) # Grootte van het assortiment dat we willen
assortiment, opt_obj = opt.solve()
Als je de code uitvoert, zou je zoiets als:
Het optimale assortiment voor het maximaliseren van de opbrengst wordt aangegeven met de indexen van de 1 waarden in de vector. Dit assortiment levert theoretisch een gemiddelde opbrengst per klant op van 134 yuan. Je kunt andere combinaties onderzoeken, maar ze zullen allemaal resulteren in een lagere gemiddelde opbrengst.
Een andere doelstelling zou het maximaliseren van het aantal verkopen kunnen zijn. In dit scenario wordt de waarde per item voor optimalisatie ingesteld op 1 voor alle items, wat leidt tot een ander optimaal assortiment.
De efficiëntie van deze methode wordt duidelijk als er extra beperkingen worden ingevoerd. Je moet bijvoorbeeld rekening houden met schapruimtebeperkingen in je winkel. In dat geval kun je optimaliseren voor een assortiment waarvan de totale artikelgrootte niet groter is dan de beschikbare schapruimte. Deze extra beperking, samen met andere zoals prijsstrategieën, wordt hier gedemonstreerd.
Conclusie
Als je werkt aan assortimentsoptimalisatie of prijsstelling, is keuzemodellering een geweldig hulpmiddel. Choice-Learn biedt veel leuke voorbeelden op GitHub. Bekijk het eens en laat een ster achter als je het nuttig vindt!