Partager via


Développer, évaluer et noter un modèle de prévision des ventes d'un hypermarché

Ce tutoriel présente un exemple de bout en bout d’un workflow Synapse Data Science dans Microsoft Fabric. Le scénario crée un modèle de prévision qui utilise des données de ventes historiques pour prédire les ventes de catégorie de produits dans un superstore.

La prévision est un actif crucial dans les ventes. Il combine des données historiques et des méthodes prédictives pour fournir des insights sur les tendances futures. Les prévisions peuvent analyser les ventes passées pour identifier les modèles et apprendre à partir du comportement des consommateurs pour optimiser les stratégies d’inventaire, de production et de marketing. Cette approche proactive améliore l’adaptabilité, la réactivité et les performances globales des entreprises dans un marché dynamique.

Ce tutoriel décrit les étapes suivantes :

  • Charger les données
  • Utiliser l’analyse exploratoire des données pour comprendre et traiter les données
  • Entraîner un modèle d'apprentissage automatique avec un paquet logiciel open source et suivre des expériences avec MLflow et la fonctionnalité de journalisation automatique Fabric.
  • Enregistrez le modèle Machine Learning final et effectuez des prédictions
  • Afficher les performances du modèle avec les visualisations Power BI

Conditions préalables

  • Procurez-vous un abonnement Microsoft Fabric . Vous pouvez également vous inscrire à une version d’évaluation gratuite de Microsoft Fabric .

  • Connectez-vous à Microsoft Fabric.

  • Utilisez le sélecteur d’expérience en bas à gauche de votre page d’accueil pour basculer vers Fabric.

    Capture d’écran du menu sélecteur d’expérience, montrant où sélectionner Data Science.

Suivre dans un notebook

Vous pouvez choisir l’une de ces options à suivre dans un bloc-notes :

  • Ouvrir et exécuter le notebook intégré dans l’expérience Synapse Data Science
  • Charger votre notebook à partir de GitHub vers l’expérience Synapse Data Science

Ouvrir le notebook intégré

L’exemple Prévisions de vente notebook accompagne ce didacticiel.

  1. Pour ouvrir le carnet d'exemple pour le didacticiel, suivez les instructions de Préparez votre système pour les didacticiels de science des données.

  2. Assurez-vous d’attacher un lakehouse au notebook avant de commencer à exécuter du code.

Importer le notebook à partir de GitHub

Le bloc-notes AIsample - Superstore Forecast.ipynb accompagne ce didacticiel.

Étape 1 : Charger les données

Le jeu de données contient 9 995 instances de ventes de différents produits. Il inclut également 21 attributs. Ce tableau provient du fichier Superstore.xlsx utilisé dans ce notebook :

ID de ligne ID de commande Date de commande Date d’expédition Mode d’expédition ID de client Nom du client Segment Pays Ville État Code postal Région ID de produit Catégorie Sous-catégorie Nom du produit Ventes Quantité Rabais Bénéfice
4 US-2015-108966 2015-10-11 2015-10-18 Classe Standard SO-20335 Sean O’Donnell Consommateur États-Unis Fort Lauderdale Floride 33311 Sud FUR-TA-10000577 Mobilier Tables Table rectangulaire élancée Série CR4500 de Bretford 957,5775 5 0.45 -383.0310
11 CA-2014-115812 2014-06-09 2014-06-09 Classe Standard Classe Standard Brosina Hoffman Consommateur États-Unis Los Angeles Californie 90032 Ouest FUR-TA-10001539 Mobilier Tables Chromcraft Tables de conférence rectangulaires 1706.184 9 0.2 85.3092
31 US-2015-150630 2015-09-17 2015-09-21 Classe Standard TB-21520 Tracy Blumstein Consommateur États-Unis Philadelphie Pennsylvanie 19140 Est OFF-EN-10001509 Fournitures de bureau Enveloppes Enveloppes à ficelle en polyéthylène 3.264 2 0.2 1.1016

Définissez ces paramètres pour pouvoir utiliser ce notebook avec différents jeux de données :

IS_CUSTOM_DATA = False  # If TRUE, the dataset has to be uploaded manually

IS_SAMPLE = False  # If TRUE, use only rows of data for training; otherwise, use all data
SAMPLE_ROWS = 5000  # If IS_SAMPLE is True, use only this number of rows for training

DATA_ROOT = "/lakehouse/default"
DATA_FOLDER = "Files/salesforecast"  # Folder with data files
DATA_FILE = "Superstore.xlsx"  # Data file name

EXPERIMENT_NAME = "aisample-superstore-forecast"  # MLflow experiment name

Téléchargez le jeu de données et chargez-le dans le lakehouse

Ce code télécharge une version publique du jeu de données et la stocke ensuite dans un lac de données Fabric.

Important

Assurez-vous d’ajouter un lakehouse au notebook avant de l’exécuter. Sinon, vous rencontrerez une erreur.

import os, requests
if not IS_CUSTOM_DATA:
    # Download data files into the lakehouse if they're not already there
    remote_url = "https://synapseaisolutionsa.blob.core.windows.net/public/Forecast_Superstore_Sales"
    file_list = ["Superstore.xlsx"]
    download_path = "/lakehouse/default/Files/salesforecast/raw"

    if not os.path.exists("/lakehouse/default"):
        raise FileNotFoundError(
            "Default lakehouse not found, please add a lakehouse and restart the session."
        )
    os.makedirs(download_path, exist_ok=True)
    for fname in file_list:
        if not os.path.exists(f"{download_path}/{fname}"):
            r = requests.get(f"{remote_url}/{fname}", timeout=30)
            with open(f"{download_path}/{fname}", "wb") as f:
                f.write(r.content)
    print("Downloaded demo data files into lakehouse.")

Configurer le suivi des expériences MLflow

Microsoft Fabric capture automatiquement les valeurs des paramètres d’entrée et des métriques de sortie d’un modèle Machine Learning lorsque vous l’entraînez. Cela étend les capacités de journalisation automatique de MLflow. Les informations sont ensuite consignées dans l’espace de travail, où vous pouvez y accéder et les visualiser avec les API MLflow ou l’expérience correspondante dans l’espace de travail. Pour en savoir plus sur l’autologging, consultez autologging dans Microsoft Fabric.

Pour désactiver la journalisation automatique de Microsoft Fabric dans une session de notebook, appelez mlflow.autolog() et définissez disable=True:

# Set up MLflow for experiment tracking
import mlflow

mlflow.set_experiment(EXPERIMENT_NAME)
mlflow.autolog(disable=True)  # Turn off MLflow autologging

Lire des données brutes à partir du lakehouse

Lisez les données brutes de la section Fichiers du lakehouse. Ajoutez d’autres colonnes pour différentes parties de date. Les mêmes informations sont utilisées pour créer une table delta partitionnée. Étant donné que les données brutes sont stockées sous forme de fichier Excel, vous devez utiliser pandas pour la lire :

import pandas as pd
df = pd.read_excel("/lakehouse/default/Files/salesforecast/raw/Superstore.xlsx")

Étape 2 : Effectuer une analyse exploratoire des données

Importer des bibliothèques

Avant toute analyse, importez les bibliothèques requises :

# Importing required libraries
import warnings
import itertools
import numpy as np
import matplotlib.pyplot as plt
warnings.filterwarnings("ignore")
plt.style.use('fivethirtyeight')
import pandas as pd
import statsmodels.api as sm
import matplotlib
matplotlib.rcParams['axes.labelsize'] = 14
matplotlib.rcParams['xtick.labelsize'] = 12
matplotlib.rcParams['ytick.labelsize'] = 12
matplotlib.rcParams['text.color'] = 'k'
from sklearn.metrics import mean_squared_error,mean_absolute_percentage_error

Afficher les données brutes

Passez en revue manuellement un sous-ensemble des données pour mieux comprendre le jeu de données lui-même et utilisez la fonction display pour imprimer le DataFrame. En outre, les vues Chart peuvent facilement visualiser des sous-ensembles du jeu de données.

display(df)

Ce notebook se concentre principalement sur la prévision des ventes de catégorie Furniture. Cela accélère le calcul et permet d’afficher les performances du modèle. Toutefois, ce notebook utilise des techniques adaptables. Vous pouvez étendre ces techniques pour prédire les ventes d’autres catégories de produits.

# Select "Furniture" as the product category
furniture = df.loc[df['Category'] == 'Furniture']
print(furniture['Order Date'].min(), furniture['Order Date'].max())

Prétraiter les données

Les scénarios d’entreprise réels doivent souvent prédire les ventes dans trois catégories distinctes :

  • Catégorie de produit spécifique
  • Catégorie de client spécifique
  • Combinaison spécifique de catégorie de produit et de catégorie client

Tout d’abord, supprimez les colonnes inutiles pour prétraiter les données. Certaines des colonnes (Row ID, Order ID,Customer IDet Customer Name) ne sont pas nécessaires, car elles n’ont aucun impact. Nous voulons prévoir les ventes globales, dans l’état et la région, pour une catégorie de produit spécifique (Furniture), afin que nous puissions supprimer les State, Region, Country, Cityet Postal Code colonnes. Pour prévoir les ventes d’un emplacement ou d’une catégorie spécifique, vous devrez peut-être ajuster l’étape de prétraitement en conséquence.

# Data preprocessing
cols = ['Row ID', 'Order ID', 'Ship Date', 'Ship Mode', 'Customer ID', 'Customer Name', 
'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category', 
'Sub-Category', 'Product Name', 'Quantity', 'Discount', 'Profit']
# Drop unnecessary columns
furniture.drop(cols, axis=1, inplace=True)
furniture = furniture.sort_values('Order Date')
furniture.isnull().sum()

Le jeu de données est structuré quotidiennement. Nous devons rééchantillonner la colonne Order Date, car nous voulons développer un modèle pour prévoir les ventes sur une base mensuelle.

Tout d’abord, regroupez la catégorie Furniture par Order Date. Ensuite, calculez la somme de la colonne Sales pour chaque groupe afin de déterminer le total des ventes pour chaque valeur de Order Date unique. Rééchantillonner la colonne Sales avec la fréquence MS, pour agréger les données par mois. Enfin, calculez la valeur moyenne des ventes pour chaque mois.

# Data preparation
furniture = furniture.groupby('Order Date')['Sales'].sum().reset_index()
furniture = furniture.set_index('Order Date')
furniture.index
y = furniture['Sales'].resample('MS').mean()
y = y.reset_index()
y['Order Date'] = pd.to_datetime(y['Order Date'])
y['Order Date'] = [i+pd.DateOffset(months=67) for i in y['Order Date']]
y = y.set_index(['Order Date'])
maximim_date = y.reset_index()['Order Date'].max()

Démontrer l’impact de Order Date sur Sales pour la catégorie Furniture :

# Impact of order date on the sales
y.plot(figsize=(12, 3))
plt.show()

Avant toute analyse statistique, vous devez importer le module statsmodels Python. Il fournit des classes et des fonctions pour l’estimation de nombreux modèles statistiques. Il fournit également des classes et des fonctions pour effectuer des tests statistiques et l’exploration des données statistiques.

import statsmodels.api as sm

Effectuer une analyse statistique

Une série chronologique suit ces éléments de données à intervalles définis pour déterminer la variation de ces éléments dans le modèle de série chronologique :

  • Niveau: composant fondamental représentant la valeur moyenne d’une période spécifique

  • Tendance: décrit si la série chronologique diminue, reste constante ou augmente au fil du temps

  • saisonnalité: décrit le signal périodique dans la série chronologique et recherche des occurrences cycliques qui affectent les modèles de série chronologique croissants ou décroissants

  • Bruit/Résiduel: Fait référence aux fluctuations aléatoires et à la variabilité des données de séries chronologiques que le modèle est incapable d'expliquer.

Dans ce code, vous observez ces éléments pour votre jeu de données après le prétraitement :

# Decompose the time series into its components by using statsmodels
result = sm.tsa.seasonal_decompose(y, model='additive')

# Labels and corresponding data for plotting
components = [('Seasonality', result.seasonal),
              ('Trend', result.trend),
              ('Residual', result.resid),
              ('Observed Data', y)]

# Create subplots in a grid
fig, axes = plt.subplots(nrows=4, ncols=1, figsize=(12, 7))
plt.subplots_adjust(hspace=0.8)  # Adjust vertical space
axes = axes.ravel()

# Plot the components
for ax, (label, data) in zip(axes, components):
    ax.plot(data, label=label, color='blue' if label != 'Observed Data' else 'purple')
    ax.set_xlabel('Time')
    ax.set_ylabel(label)
    ax.set_xlabel('Time', fontsize=10)
    ax.set_ylabel(label, fontsize=10)
    ax.legend(fontsize=10)

plt.show()

Les tracés décrivent la saisonnalité, les tendances et le bruit dans les données de prévision. Vous pouvez capturer les modèles sous-jacents et développer des modèles qui font des prédictions précises résilientes aux fluctuations aléatoires.

Étape 3 : Entraîner et suivre le modèle

Maintenant que vous disposez des données disponibles, définissez le modèle de prévision. Dans ce notebook, appliquez le modèle de prévision appelé modèle autorégressif intégré à moyenne mobile saisonnière avec des facteurs exogènes (SARIMAX). SARIMAX combine les composants autorégressifs (AR) et de moyenne mobile (MA), la différenciation saisonnière et les prédicteurs externes pour effectuer des prévisions précises et flexibles pour les données de série chronologique.

Vous utilisez également la journalisation automatique de MLflow et Fabric pour suivre les expériences. Ici, chargez la table Delta depuis le lakehouse. Vous pouvez utiliser d’autres tables delta qui considèrent le lac comme source.

# Import required libraries for model evaluation
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error

Ajuster les hyperparamètres

SARIMAX prend en compte les paramètres impliqués dans le mode de moyenne mobile intégrée intégrée standard (ARIMA) (p, d, q) et ajoute les paramètres de saisonnalité (P, D, Q, s). Ces arguments de modèle SARIMAX sont appelés (p, d, q) et d’ordre saisonnier (P, D, Q, s), respectivement. Par conséquent, pour entraîner le modèle, nous devons d’abord régler sept paramètres.

Paramètres d’ordre :

  • p: ordre du composant AR, représentant le nombre d’observations passées dans la série chronologique utilisée pour prédire la valeur actuelle.

    En règle générale, ce paramètre doit être un entier non négatif. Les valeurs courantes se trouvent dans la plage de 0 à 3, bien que les valeurs supérieures soient possibles, en fonction des caractéristiques de données spécifiques. Une valeur p supérieure indique une mémoire plus longue des valeurs passées dans le modèle.

  • d: ordre de différenciation, représentant le nombre de fois où la série chronologique doit être différente, pour atteindre la stationarité.

    Ce paramètre doit être un entier non négatif. Les valeurs courantes se trouvent dans la plage de 0 à 2. Une valeur d de 0 signifie que la série chronologique est déjà stationnaire. Les valeurs supérieures indiquent le nombre d’opérations de différenciation requises pour le rendre stationnaire.

  • q: ordre du composant MA, représentant le nombre de termes d’erreur de bruit blanc passés utilisés pour prédire la valeur actuelle.

    Ce paramètre doit être un entier non négatif. Les valeurs courantes se trouvent dans la plage de 0 à 3, mais des valeurs plus élevées peuvent être nécessaires pour certaines séries chronologiques. Une valeur q plus élevée indique une plus forte dépendance vis-à-vis des termes d’erreur passés pour effectuer des prédictions.

Paramètres de l’ordre saisonnier :

  • P: Ordre saisonnier du composant AR, similaire à p mais pour la partie saisonnière
  • D: Ordre saisonnier de différenciation, similaire à d mais pour la partie saisonnière
  • Q: Ordre saisonnier du composant MA, similaire à q mais pour la partie saisonnière
  • s: nombre d’étapes de temps par cycle saisonnier (par exemple, 12 pour les données mensuelles avec une saisonnalité annuelle)
# Hyperparameter tuning
p = d = q = range(0, 2)
pdq = list(itertools.product(p, d, q))
seasonal_pdq = [(x[0], x[1], x[2], 12) for x in list(itertools.product(p, d, q))]
print('Examples of parameter combinations for Seasonal ARIMA...')
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[1]))
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[2]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[3]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[4]))

SARIMAX a d’autres paramètres :

  • enforce_stationarity: indique si le modèle doit appliquer la stationarité sur les données de série chronologique avant d’ajuster le modèle SARIMAX.

    Si enforce_stationarity est défini sur True (valeur par défaut), il indique que le modèle SARIMAX doit appliquer la stationarité sur les données de série chronologique. Le modèle SARIMAX applique ensuite automatiquement la différenciation aux données, pour les rendre stationnaires, comme spécifié par les ordres d et D, avant d’adapter le modèle. Il s’agit d’une pratique courante, car de nombreux modèles de série chronologique, y compris SARIMAX, supposent que les données sont stationnaires.

    Pour une série chronologique nonstationnaire (par exemple, elle présente des tendances ou une saisonnalité), il est recommandé de définir enforce_stationarity sur True, et laisser le modèle SARIMAX gérer la différenciation pour atteindre la stationarité. Pour une série chronologique stationnaire (par exemple, sans tendance ni saisonnalité), définissez enforce_stationarity sur False pour éviter les différences inutiles.

  • enforce_invertibility: contrôle si le modèle doit appliquer ou non l’invertibilité sur les paramètres estimés pendant le processus d’optimisation.

    Si enforce_invertibility est défini sur True (valeur par défaut), il indique que le modèle SARIMAX doit appliquer l’invertibilité sur les paramètres estimés. L’invertibilité garantit que le modèle est bien défini et que les coefficients AR et MA estimés se trouvent dans la plage de stationarité.

    L’application de l’invertibilité permet de s’assurer que le modèle SARIMAX respecte les exigences théoriques d’un modèle de série chronologique stable. Il permet également d’éviter les problèmes liés à l’estimation et à la stabilité du modèle.

La valeur par défaut est un modèle AR(1). Cela fait référence à (1, 0, 0). Toutefois, il est courant d’essayer différentes combinaisons des paramètres d’ordre et des paramètres d’ordre saisonnier, et d’évaluer les performances du modèle pour un jeu de données. Les valeurs appropriées peuvent varier d’une série chronologique à une autre.

La détermination des valeurs optimales implique souvent l’analyse de la fonction de correction automatique (ACF) et la fonction de correction automatique partielle (PACF) des données de série chronologique. Il implique également souvent l’utilisation de critères de sélection de modèle ( par exemple, le critère d’information Akaike (AIC) ou le critère d’information bayésien (BIC).

Paramétrez les hyperparamètres :

# Tune the hyperparameters to determine the best model
for param in pdq:
    for param_seasonal in seasonal_pdq:
        try:
            mod = sm.tsa.statespace.SARIMAX(y,
                                            order=param,
                                            seasonal_order=param_seasonal,
                                            enforce_stationarity=False,
                                            enforce_invertibility=False)
            results = mod.fit(disp=False)
            print('ARIMA{}x{}12 - AIC:{}'.format(param, param_seasonal, results.aic))
        except:
            continue

Après l’évaluation des résultats précédents, vous pouvez déterminer les valeurs des paramètres de commande et des paramètres d’ordre saisonnier. Le choix est order=(0, 1, 1) et seasonal_order=(0, 1, 1, 12), qui offrent le niveau AIC le plus bas (par exemple, 279,58). Utilisez ces valeurs pour entraîner le modèle.

Entraîner le modèle

# Model training 
mod = sm.tsa.statespace.SARIMAX(y,
                                order=(0, 1, 1),
                                seasonal_order=(0, 1, 1, 12),
                                enforce_stationarity=False,
                                enforce_invertibility=False)
results = mod.fit(disp=False)
print(results.summary().tables[1])

Ce code visualise une prévision de série chronologique pour les données de ventes de meubles. Les résultats tracés indiquent à la fois les données observées et la prévision d’une étape à l’avance, avec une région ombrée pour l’intervalle de confiance.

# Plot the forecasting results
pred = results.get_prediction(start=maximim_date, end=maximim_date+pd.DateOffset(months=6), dynamic=False) # Forecast for the next 6 months (months=6)
pred_ci = pred.conf_int() # Extract the confidence intervals for the predictions
ax = y['2019':].plot(label='observed')
pred.predicted_mean.plot(ax=ax, label='One-step ahead forecast', alpha=.7, figsize=(12, 7))
ax.fill_between(pred_ci.index,
                pred_ci.iloc[:, 0],
                pred_ci.iloc[:, 1], color='k', alpha=.2)
ax.set_xlabel('Date')
ax.set_ylabel('Furniture Sales')
plt.legend()
plt.show()
# Validate the forecasted result
predictions = results.get_prediction(start=maximim_date-pd.DateOffset(months=6-1), dynamic=False)
# Forecast on the unseen future data
predictions_future = results.get_prediction(start=maximim_date+ pd.DateOffset(months=1),end=maximim_date+ pd.DateOffset(months=6),dynamic=False)

Utilisez predictions pour évaluer les performances du modèle, en la comparant aux valeurs réelles. La valeur predictions_future indique les prévisions futures.

# Log the model and parameters
model_name = f"{EXPERIMENT_NAME}-Sarimax"
with mlflow.start_run(run_name="Sarimax") as run:
    mlflow.statsmodels.log_model(results,model_name,registered_model_name=model_name)
    mlflow.log_params({"order":(0,1,1),"seasonal_order":(0, 1, 1, 12),'enforce_stationarity':False,'enforce_invertibility':False})
    model_uri = f"runs:/{run.info.run_id}/{model_name}"
    print("Model saved in run %s" % run.info.run_id)
    print(f"Model URI: {model_uri}")
mlflow.end_run()
# Load the saved model
loaded_model = mlflow.statsmodels.load_model(model_uri)

Étape 4 : Noter le modèle et enregistrer les prédictions

Intégrez les valeurs réelles aux valeurs prévues pour créer un rapport Power BI. Stockez ces résultats dans une table au sein du lakehouse.

# Data preparation for Power BI visualization
Future = pd.DataFrame(predictions_future.predicted_mean).reset_index()
Future.columns = ['Date','Forecasted_Sales']
Future['Actual_Sales'] = np.NAN
Actual = pd.DataFrame(predictions.predicted_mean).reset_index()
Actual.columns = ['Date','Forecasted_Sales']
y_truth = y['2023-02-01':]
Actual['Actual_Sales'] = y_truth.values
final_data = pd.concat([Actual,Future])
# Calculate the mean absolute percentage error (MAPE) between 'Actual_Sales' and 'Forecasted_Sales' 
final_data['MAPE'] = mean_absolute_percentage_error(Actual['Actual_Sales'], Actual['Forecasted_Sales']) * 100
final_data['Category'] = "Furniture"
final_data[final_data['Actual_Sales'].isnull()]
input_df = y.reset_index()
input_df.rename(columns = {'Order Date':'Date','Sales':'Actual_Sales'}, inplace=True)
input_df['Category'] = 'Furniture'
input_df['MAPE'] = np.NAN
input_df['Forecasted_Sales'] = np.NAN
# Write back the results into the lakehouse
final_data_2 = pd.concat([input_df,final_data[final_data['Actual_Sales'].isnull()]])
table_name = "Demand_Forecast_New_1"
spark.createDataFrame(final_data_2).write.mode("overwrite").format("delta").save(f"Tables/{table_name}")
print(f"Spark DataFrame saved to delta table: {table_name}")

Étape 5 : Visualiser dans Power BI

Le rapport Power BI affiche une erreur de pourcentage absolu moyen (MAPE) de 16,58. La métrique MAPE définit la précision d’une méthode de prévision. Il représente la précision des quantités prévues, par rapport aux quantités réelles.

MAPE est une métrique simple. Un 10% MAPE représente que l’écart moyen entre les valeurs prévues et les valeurs réelles est de 10%, que l’écart ait été positif ou négatif. Les normes de valeurs MAPE souhaitables varient selon les secteurs d’activité.

La ligne bleue claire de ce graphique représente les valeurs de ventes réelles. La ligne bleue foncée représente les valeurs de ventes prévues. La comparaison des ventes réelles et prévues révèle que le modèle prédit efficacement les ventes pour la catégorie Furniture au cours des six premiers mois de 2023.

Capture d’écran d’un rapport Power BI.

En fonction de cette observation, nous pouvons avoir confiance dans les capacités de prévision du modèle, pour les ventes globales au cours des six derniers mois de 2023, et s’étendre en 2024. Cette confiance peut informer les décisions stratégiques relatives à la gestion de l’inventaire, à l’approvisionnement des matières premières et à d’autres considérations relatives à l’entreprise.