Leia nosso artigo sobre

class="lazyload

A otimização do sortimento é um processo crítico no varejo que envolve a seleção do mix ideal de produtos para atender à demanda do consumidor, levando em conta as diversas restrições logísticas envolvidas. Os varejistas precisam ter certeza de que estão oferecendo os produtos certos, nas quantidades certas e no momento certo. Ao aproveitar os data e as percepções dos consumidores, os varejistas podem tomar decisões informadas sobre quais itens estocar, como gerenciar o estoque e quais produtos priorizar com base nas preferências dos clientes, nas tendências sazonais e nos padrões de vendas.

Para as empresas de varejo, a otimização do sortimento é essencial para atingir um equilíbrio entre variedade e eficiência. Oferecer poucas opções pode afastar os clientes, enquanto oferecer muitas pode gerar confusão, excesso de estoque e margens de lucro menores. A otimização do sortimento de produtos ajuda as empresas a aumentar a satisfação do cliente, garantindo a disponibilidade de itens populares e eliminando produtos de baixo desempenho que ocupam um espaço valioso nas prateleiras.

A modelagem de escolha é uma maneira eficiente de abordar a otimização do sortimento porque oferece uma estrutura data para entender as preferências dos clientes e prever como eles escolherão entre diferentes produtos. Ao analisar vários fatores, como sensibilidade ao preço, características do produto e fidelidade à marca, a modelagem de escolha ajuda os varejistas a identificar quais produtos têm maior probabilidade de atender à demanda dos clientes.

Em última análise, a modelagem de escolhas permite que os varejistas ofereçam o mix certo de produtos, personalizem sortimentos para segmentos específicos de clientes e também otimizem o espaço nas prateleiras para aumentar a lucratividade ou até mesmo o preço dos itens.

Se você nunca ouviu falar em modelagem de escolha, leia nosso artigo que apresenta os principais conceitos com exemplos. Neste artigo, vamos nos concentrar principalmente em como os modelos de escolha discreta podem ser usados para otimizar uma variedade de produtos. Fornecemos exemplos de código com base na biblioteca choice-learn, que foi projetada para ajudar os cientistas data nesses casos de uso.

O código fornecido usa o pacote Python choice-learn e pode ser encontrado em um notebook aqui.

Configurar: Instalação do Python e do Choice-Learn

Neste artigo, fornecemos trechos de código para acompanhar as explicações. O código usa a biblioteca Choice-Learn, que fornece ferramentas eficientes para modelagem de escolhas e várias aplicações, como otimização de sortimento ou preço. A Choice-Learn está disponível no PyPI, e você pode obtê-la simplesmente com


pip install choice-learn

O conjunto de dados: recibos de vendas

Usaremos o conjunto de dados de mercearias TaFeng. Você pode baixá-lo do Kaggle e abri-lo em seu ambiente Python com choice-learn:

from choice_learn.datasets import load_tafeng
tafeng_df = load_tafeng(as_frame=True)
print(tafeng_df.head())
class="lazyload

O conjunto de dados consiste em mais de 800.000 compras individuais em uma mercearia chinesa. Para cada compra, são fornecidos vários detalhes, inclusive o item comprado (PRODUCT_ID), o preço pelo qual foi vendido (SALES_PRICE) e a faixa etária do cliente (AGE_GROUP).

Você pode observar que muitos itens diferentes são fornecidos e alguns deles raramente são vendidos. Para otimizar a logística, o varejista pode optar por reduzir o número de produtos que oferece. A meta nesse caso é identificar o subconjunto ideal de itens a serem vendidos.

Para isso, concentramo-nos nos itens mais vendidos, pois eles têm maior probabilidade de serem comprados novamente e desempenharão um papel crucial na formação de um sortimento mais eficiente e lucrativo. Observe que fazemos isso principalmente para simplificar o exemplo e que todos os itens poderiam ser mantidos.

# Manter apenas os 20 produtos mais vendidos
tafeng_df = tafeng_df.loc[
tafeng_df.PRODUCT_ID.isin(tafeng_df.PRODUCT_ID.value_counts().index[:20])
].reset_index(drop=True)
# Remover valores NaN
tafeng_df = tafeng_df.loc[
tafeng_df.AGE_GROUP.isin(["25-29", "40-44", "45-49", ">65", "30-34", "35-39", "50-54", "55-59", "60-64"] )
].reset_index(drop=True)
print(tafeng_df.head())

Vamos também codificar as categorias de idade com um valor quente a cada dez anos:

# Codificação das categorias de idade
tafeng_df["twenties"]= tafeng_df.apply(lambda row: 1 if row["AGE_GROUP"]== "25-29" else 0, axis=1)
tafeng_df["thirties"]= tafeng_df.apply(
lambda row: 1 if row["AGE_GROUP"] in (["30-34", "35-39"]) else 0, axis=1
)
tafeng_df["forties"]= tafeng_df.apply(
lambda row: 1 if row["AGE_GROUP"] in (["40-44", "45-49"]) else 0, axis=1
)
tafeng_df["fifties"]= tafeng_df.apply(
lambda row: 1 if row["AGE_GROUP"] in (["50-54", "55-59"]) else 0, axis=1
)
tafeng_df["sixties_and_above"]= tafeng_df.apply(
lambda row: 1 if row["AGE_GROUP"] in (["60-64", ">65"]) else 0, axis=1
)

Agora que nossos data estão prontos, precisamos criar um ChoiceDataset, o objeto manipulador de data no choice-learn. Isso envolve a especificação dos recursos que descrevem o contexto em que uma compra é feita:

  • Características do cliente (recursos compartilhados): a categoria de idade
  • Características do produto (recursos do item): o preço do item

Um aspecto importante da modelagem de escolha é que exigimos as características de todos os itens disponíveis no momento da compra, não apenas o escolhido. Isso nos permite analisar como os preços de diferentes produtos influenciam a decisão do cliente. Como essas informações não estão diretamente disponíveis no conjunto de dados, presumimos que, para cada compra, os preços dos outros itens permanecem os mesmos da venda anterior.

# product ID to index
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)

Agora que temos todas as nossas informações, podemos criar o ChoiceDataset:

from choice_learndata import ChoiceDataset
dataset = ChoiceDataset(
shared_features_by_choice=shared_features,
shared_features_by_choice_names=['twenties', 'thirties', 'forties', 'fifties', 'sixties_and_above'],
items_features_by_choice=itens_features,
items_features_by_choice_names=["SALES_PRICE"],
choices=choices
)

Definição e estimativa do modelo de escolha

Desenvolveremos e estimaremos um modelo de escolha que prevê a probabilidade de um cliente selecionar um item específico em um sortimento completo de produtos semelhantes. Com base no conjunto de dados disponível, definimos a seguinte função de utilidade para um item i considerado por um cliente j:

class="lazyload

Essa função representa a utilidade (ou satisfação) que um cliente obtém ao escolher um determinado item, influenciada tanto pela idade do cliente quanto pelo preço do item.

Para obter mais detalhes sobre como formulamos uma função de utilidade, consulte nossa primeira postagem. Observe que outro modelo lógico - mas não apresentado para simplificar - poderia ser estimar uma sensibilidade de preço por categoria de idade.

Aqui está o código para estimar esse modelo com choice-learn:

from choice_learn.models import ConditionalLogit
model = ConditionalLogit(optimizer="Adam",batch_size=1024, epochs=300, lr=0.002)
for age_category in ["twenties", "thirties", "forties", " fifties", "sixties_and_above"]:
model.add_coefficients(
coefficient_name=age_category, feature_name=age_category, items_indexes=list(range(20))
)
model.add_shared_coefficient(
coefficient_name="price", feature_name="SALES_PRICE",items_indexes=list(range(20))
)
hist = model.fit(dataset)

Você pode verificar se o modelo se ajusta bem ao conjunto de dados:

import matplotlib.pyplot as plt
plt.plot(hist["perda_de_treino"])
plt.xlabel("Epoch")
plt.ylabel("Negative Log Likelihood")
plt.show(
class="lazyload

Encontrar o sortimento ideal

Com as probabilidades de compra em mãos, podemos agora estimar a receita média por cliente de um sortimento A usando a fórmula:

class="lazyload

Para encontrar o sortimento que maximiza a receita, poderíamos avaliar todas as combinações possíveis e selecionar aquela com a maior receita média. Entretanto, uma abordagem mais eficiente é usar Programação Linear (LP). Aqui, vamos nos concentrar em como usar a implementação de aprendizagem por escolha do otimizador de sortimento.

É importante distinguir entre maximizar a receita e maximizar as margens de lucro. Embora a receita seja importante, as margens de lucro levam em conta os custos associados a cada produto. Dependendo de sua meta, talvez você queira otimizar o lucro em vez da receita pura.

Para otimizar o sortimento, precisamos fornecer várias informações importantes:

  • O peso que queremos dar a cada categoria de idade, vamos usar a participação dos clientes
  • A utilidade de cada item (calculada por nosso modelo de escolha) para cada categoria de idade
  • O valor a ser otimizado para cada item (neste caso, receita)
  • O tamanho do sortimento (por exemplo, 12 itens)

Veja como isso funciona usando o aprendizado por escolha:


from choice_learn.toolbox.assortment_optimizer import LatentClassAssortmentOptimizer
# Preço de cada item
future_prices = np.stack([items_features[-1]]*5, axis=0)
age_category = np.eye(5).astype("float32")
# Calcular a utilidade de cada item com base em seu preço e em cada categoria de idade
predicted_utilities = model.compute_batch_utility(shared_features_by_choice=age_category,
                                                  items_features_by_choice=future_prices,
                                                  itens_disponíveis_por_escolha=np.ones((5, 20)),
                                                 choices=None
                                                  )
age_category_weights = np.sum(shared_features, axis=0) / len(shared_features)
opt = LatentClassAssortmentOptimizer(
solver="or-tools", # Solver a ser usado, seja "or-tools" ou "gurobi" (se você tiver uma licença)
class_weights=age_category_weights, # Pesos de cada classe
class_utilities=np.exp(predicted_utilities), # utilidades na forma (n_classes, n_items)
itemwise_values=future_prices[0][:, 0], # Valores a serem otimizados para cada item, aqui o preço que é usado para calcular a rotatividade
assortment_size=12) # Tamanho do sortimento que desejamos
assortment, opt_obj = opt.solve()

Ao executar o código, você deverá ter algo como:


class="lazyload

O sortimento ideal para maximizar a receita é indicado com os índices dos valores 1 no vetor. Teoricamente, esse sortimento gera uma receita média por cliente de 134 yuans. Você pode explorar outras combinações, mas todas elas resultarão em uma receita média menor.

Outro objetivo poderia ser maximizar o número de vendas. Nesse cenário, o valor por item para otimização é definido como 1 para todos os itens, o que leva a um sortimento ideal diferente.

A eficiência desse método fica evidente quando são introduzidas restrições adicionais. Por exemplo, talvez seja necessário levar em conta as limitações de espaço nas prateleiras da loja. Nesse caso, é possível otimizar um sortimento cujo tamanho total do item não exceda o espaço disponível nas prateleiras. Essa restrição adicional, juntamente com outras, como estratégias de preços, é demonstrada aqui.


Conclusão

Se estiver trabalhando com otimização de sortimento ou precificação, a modelagem de escolha é uma ótima ferramenta, não deixe de dar uma olhada nela. O Choice-Learn fornece muitos exemplos interessantes em seu GitHub. Dê uma olhada e deixe uma estrela se você achar útil!