	{"id":333207,"date":"2025-02-19T09:51:49","date_gmt":"2025-02-19T09:51:49","guid":{"rendered":"https:\/\/www.artefact.com\/?post_type=blog&#038;p=333207"},"modified":"2025-02-27T14:30:53","modified_gmt":"2025-02-27T14:30:53","slug":"assortment-optimization-with-discrete-choice-models-in-python","status":"publish","type":"blog","link":"https:\/\/www.artefact.com\/nl\/blog\/assortment-optimization-with-discrete-choice-models-in-python\/","title":{"rendered":"Assortimentoptimalisatie met discrete keuzemodellen in Python"},"content":{"rendered":"<p><div class=\"fusion-fullwidth fullwidth-box fusion-builder-row-1 fusion-flex-container nonhundred-percent-fullwidth non-hundred-percent-height-scrolling article-author\" style=\"--awb-border-radius-top-left:0px;--awb-border-radius-top-right:0px;--awb-border-radius-bottom-right:0px;--awb-border-radius-bottom-left:0px;--awb-background-color:#ffffff;--awb-flex-wrap:wrap;\" ><div class=\"fusion-builder-row fusion-row fusion-flex-align-items-flex-start fusion-flex-content-wrap\" style=\"max-width:calc( 1440px + 20px );margin-left: calc(-20px \/ 2 );margin-right: calc(-20px \/ 2 );\"><div class=\"fusion-layout-column fusion_builder_column fusion-builder-column-0 fusion_builder_column_1_2 1_2 fusion-flex-column\" style=\"--awb-bg-size:cover;--awb-width-large:50%;--awb-margin-top-large:0px;--awb-spacing-right-large:10px;--awb-margin-bottom-large:0px;--awb-spacing-left-large:10px;--awb-width-medium:50%;--awb-order-medium:0;--awb-spacing-right-medium:10px;--awb-spacing-left-medium:10px;--awb-width-small:100%;--awb-order-small:0;--awb-spacing-right-small:10px;--awb-spacing-left-small:10px;\"><div class=\"fusion-column-wrapper fusion-column-has-shadow fusion-flex-justify-content-flex-start fusion-content-layout-column\"><div class=\"fusion-title title fusion-title-1 fusion-sep-none fusion-title-text fusion-title-size-two\" style=\"--awb-margin-bottom-small:8px;\"><h2 class=\"fusion-title-heading title-heading-left fusion-responsive-typography-calculated\" style=\"margin:0;--fontSize:50;line-height:1.2;\">Author<\/h2><\/div><img decoding=\"async\" src=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27150%27%20height%3D%270%27%20viewBox%3D%270%200%20150%200%27%3E%3Crect%20width%3D%27150%27%20height%3D%270%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-orig-src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Vincent-Auriau.jpg\" alt=\"Image\" class=\"lazyload artefact-elegant-image align-left article-author-image\" style=\"width: 150px; border-radius: 54% 46% 77% 23% \/ 74% 40% 60% 26%; overflow: hidden;\" width=\"150\" height=\"auto\" \/><div class=\"fusion-title title fusion-title-2 fusion-sep-none fusion-title-text fusion-title-size-three article-author-name-title\" style=\"--awb-text-color:var(--awb-color7);--awb-margin-bottom-small:8px;--awb-font-size:18px;\"><h3 class=\"fusion-title-heading title-heading-left fusion-responsive-typography-calculated\" style=\"font-family:&quot;Josefin Sans&quot;;font-style:normal;font-weight:600;margin:0;font-size:1em;--fontSize:18;line-height:1.5;\">Vincent Auriau<\/h3><\/div><div class=\"fusion-text fusion-text-1 article-author-description\" style=\"--awb-font-size:14px;--awb-line-height:1.6;--awb-letter-spacing:2px;--awb-text-transform:uppercase;--awb-text-color:var(--awb-color7);--awb-text-font-family:&quot;Roboto&quot;;--awb-text-font-style:normal;--awb-text-font-weight:400;\"><p>Machine Learning PhD Candidate at Artefact<\/p>\n<\/div><\/div><\/div><\/div><\/div><div class=\"fusion-fullwidth fullwidth-box fusion-builder-row-2 fusion-flex-container nonhundred-percent-fullwidth non-hundred-percent-height-scrolling\" style=\"--awb-border-radius-top-left:0px;--awb-border-radius-top-right:0px;--awb-border-radius-bottom-right:0px;--awb-border-radius-bottom-left:0px;--awb-margin-top:40px;--awb-margin-bottom:40px;--awb-flex-wrap:wrap;\" ><div class=\"fusion-builder-row fusion-row fusion-flex-align-items-center fusion-flex-justify-content-center fusion-flex-content-wrap\" style=\"max-width:calc( 1440px + 20px );margin-left: calc(-20px \/ 2 );margin-right: calc(-20px \/ 2 );\"><div class=\"fusion-layout-column fusion_builder_column fusion-builder-column-1 fusion_builder_column_1_1 1_1 fusion-flex-column fusion-flex-align-self-center fusion-column-inner-bg-wrapper\" style=\"--awb-padding-top:20px;--awb-padding-right:20px;--awb-padding-bottom:20px;--awb-padding-left:20px;--awb-overflow:hidden;--awb-inner-bg-size:cover;--awb-border-color:rgba(10,17,40,0.1);--awb-border-top:1px;--awb-border-right:1px;--awb-border-bottom:1px;--awb-border-left:1px;--awb-border-style:solid;--awb-border-radius:4px 4px 4px 4px;--awb-inner-bg-border-radius:4px 4px 4px 4px;--awb-inner-bg-overflow:hidden;--awb-width-large:100%;--awb-margin-top-large:0px;--awb-spacing-right-large:10px;--awb-margin-bottom-large:0px;--awb-spacing-left-large:10px;--awb-width-medium:100%;--awb-order-medium:0;--awb-spacing-right-medium:10px;--awb-spacing-left-medium:10px;--awb-width-small:100%;--awb-order-small:0;--awb-spacing-right-small:10px;--awb-spacing-left-small:10px;\" data-scroll-devices=\"small-visibility,medium-visibility,large-visibility\"><span class=\"fusion-column-inner-bg hover-type-none\"><a class=\"fusion-column-anchor\" href=\"https:\/\/medium.com\/artefact-engineering-and-data-science\/assortment-optimization-with-discrete-choice-models-in-python-2efc4d9a4aba\" rel=\"noopener noreferrer\" target=\"_blank\"><span class=\"fusion-column-inner-bg-image\"><\/span><\/a><\/span><div class=\"fusion-column-wrapper fusion-column-has-shadow fusion-flex-justify-content-center fusion-content-layout-row fusion-flex-align-items-center\"><div class=\"fusion-text fusion-text-2\"><p><u>Read our article on<\/u><\/p>\n<\/div><div class=\"fusion-image-element\" style=\"--awb-margin-right:20px;--awb-margin-left:20px;--awb-max-width:150px;--awb-caption-title-font-family:var(--h2_typography-font-family);--awb-caption-title-font-weight:var(--h2_typography-font-weight);--awb-caption-title-font-style:var(--h2_typography-font-style);--awb-caption-title-size:var(--h2_typography-font-size);--awb-caption-title-transform:var(--h2_typography-text-transform);--awb-caption-title-line-height:var(--h2_typography-line-height);--awb-caption-title-letter-spacing:var(--h2_typography-letter-spacing);\"><span class=\" fusion-imageframe imageframe-none imageframe-1 hover-type-none\"><a class=\"fusion-no-lightbox\" href=\"https:\/\/medium.com\/artefact-engineering-and-data-science\/assortment-optimization-with-discrete-choice-models-in-python-2efc4d9a4aba\" target=\"_self\" aria-label=\"Medium Blog\" rel=\"noopener\"><img decoding=\"async\" width=\"1024\" height=\"254\" src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2021\/04\/Medium-Blog.png\" data-orig-src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2021\/04\/Medium-Blog-1024x254.png\" alt class=\"lazyload img-responsive wp-image-60582\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%274000%27%20height%3D%27992%27%20viewBox%3D%270%200%204000%20992%27%3E%3Crect%20width%3D%274000%27%20height%3D%27992%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2021\/04\/Medium-Blog-200x50.png 200w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2021\/04\/Medium-Blog-400x99.png 400w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2021\/04\/Medium-Blog-600x149.png 600w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2021\/04\/Medium-Blog-800x198.png 800w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2021\/04\/Medium-Blog-1200x298.png 1200w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 640px) 100vw, 1024px\" \/><\/a><\/span><\/div><div class=\"fusion-text fusion-text-3\"><\/div><\/div><\/div><\/div><\/div><article class=\"fusion-fullwidth fullwidth-box fusion-builder-row-3 fusion-flex-container nonhundred-percent-fullwidth non-hundred-percent-height-scrolling\" style=\"--link_color: var(--awb-color6);--awb-border-radius-top-left:0px;--awb-border-radius-top-right:0px;--awb-border-radius-bottom-right:0px;--awb-border-radius-bottom-left:0px;--awb-flex-wrap:wrap;\" ><div class=\"fusion-builder-row fusion-row fusion-flex-align-items-flex-start fusion-flex-justify-content-center fusion-flex-content-wrap\" style=\"max-width:calc( 1440px + 20px );margin-left: calc(-20px \/ 2 );margin-right: calc(-20px \/ 2 );\"><div class=\"fusion-layout-column fusion_builder_column fusion-builder-column-2 fusion_builder_column_1_1 1_1 fusion-flex-column\" style=\"--awb-bg-size:cover;--awb-width-large:100%;--awb-margin-top-large:0px;--awb-spacing-right-large:10px;--awb-margin-bottom-large:0px;--awb-spacing-left-large:10px;--awb-width-medium:100%;--awb-order-medium:0;--awb-spacing-right-medium:10px;--awb-spacing-left-medium:10px;--awb-width-small:100%;--awb-order-small:0;--awb-spacing-right-small:10px;--awb-spacing-left-small:10px;\"><div class=\"fusion-column-wrapper fusion-column-has-shadow fusion-flex-justify-content-flex-start fusion-content-layout-column\"><div class=\"fusion-text fusion-text-4\"><p>Assortment optimization is a critical process in retail that involves\u00a0<strong class=\"mw gv\">curating the ideal mix of products<\/strong>\u00a0to meet consumer demand while taking into account the many logistics constraints involved. The retailers need to make sure that they offer the right products, in the right quantities, at the right time. By leveraging data and consumer insights, retailers can make informed decisions on which items to stock, how to manage inventory, and what products to prioritize based on customer preferences, seasonal trends, and sales patterns.<\/p>\n<p id=\"a6ea\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">For retail businesses, assortment optimization is essential to striking a balance between\u00a0<strong class=\"mw gv\">variety<\/strong>\u00a0and\u00a0<strong class=\"mw gv\">efficiency<\/strong>. Offering too few choices may drive customers away, while offering too many can lead to confusion, excess inventory, and lower profit margins. Optimizing the product assortment helps businesses enhance customer satisfaction by ensuring popular items are available while eliminating underperforming products that take up valuable shelf space.<\/p>\n<p id=\"3d84\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\"><strong class=\"mw gv\">Choice modeling<\/strong>\u00a0is an efficient way to approach assortment optimization because it provides a data-driven framework for understanding customer preferences and predicting how they will choose between different products. By analyzing various factors such as price sensitivity, product features, and brand loyalty, choice modeling helps retailers identify which products are most likely to meet customer demand.<\/p>\n<\/div><div class=\"fusion-text fusion-text-5\"><p id=\"8a71\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">Ultimately, choice modeling enables retailers to offer the right mix of products, tailor assortments to specific customer segments, and can also optimize shelf space to drive profitability or even the pricing of items.<\/p>\n<p id=\"7e00\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">If you have never heard of choice modeling, you can read\u00a0<a class=\"af ol\" href=\"https:\/\/medium.com\/artefact-engineering-and-data-science\/modeling-customers-decisions-in-python-with-the-choice-learn-package-37752cb7932e\" rel=\"noopener\" target=\"_blank\">our article<\/a>\u00a0that introduces the key concepts with examples. In this article, we will mainly focus on how discrete choice models can be used to optimize an assortment of products. We provide code samples based on the\u00a0<a class=\"af ol\" href=\"https:\/\/github.com\/artefactory\/choice-learn\" target=\"_blank\" rel=\"noopener ugc nofollow\">choice-learn<\/a>\u00a0library, which is designed to help data scientists on such use cases.<\/p>\n<p id=\"7f9a\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\"><em class=\"ns\">The provided code uses the choice-learn Python package and can be found in a notebook\u00a0<\/em><a class=\"af ol\" href=\"https:\/\/github.com\/VincentAuriau\/choice-learn-tutorials\/blob\/main\/notebooks\/assortment-optimization.ipynb\" target=\"_blank\" rel=\"noopener ugc nofollow\"><strong class=\"mw gv\"><em class=\"ns\">here<\/em><\/strong><\/a><em class=\"ns\">.<\/em><\/p>\n<\/div><div class=\"fusion-title title fusion-title-3 fusion-sep-none fusion-title-text fusion-title-size-two\" style=\"--awb-margin-bottom-small:8px;--awb-font-size:30px;\"><h2 class=\"fusion-title-heading title-heading-left fusion-responsive-typography-calculated\" style=\"font-family:&quot;PT Serif&quot;;font-style:normal;font-weight:700;margin:0;letter-spacing:1.6xp;font-size:1em;--fontSize:30;line-height:1.47;\">Set up: Installing Python &amp; Choice-Learn<\/h2><\/div><div class=\"fusion-text fusion-text-6\"><p id=\"212a\" class=\"pw-post-body-paragraph mu mv gu mw b mx pk mz na nb pl nd ne nf pm nh ni nj pn nl nm nn po np nq nr gn bk\" data-selectable-paragraph=\"\">In this article, we provide code snippets to accompany the explanations. The code uses the\u00a0<a class=\"af ol\" href=\"https:\/\/github.com\/artefactory\/choice-learn\" target=\"_blank\" rel=\"noopener ugc nofollow\">Choice-Learn<\/a>\u00a0library, which provides efficient tools for choice modeling and several applications \u2014 such as assortment optimization or price. Choice-Learn is available through PyPI, you can get it simply with<\/p>\n<pre class=\"nw nx ny nz oa pp pq pr bp ps bb bk\"><\/pre>\n<\/div><div class=\"fusion-text fusion-text-7\"><div id=\"212a\" class=\"pw-post-body-paragraph mu mv gu mw b mx pk mz na nb pl nd ne nf pm nh ni nj pn nl nm nn po np nq nr gn bk code\">pip install choice-learn<\/div>\n<pre class=\"nw nx ny nz oa pp pq pr bp ps bb bk\"><\/pre>\n<\/div><div class=\"fusion-title title fusion-title-4 fusion-sep-none fusion-title-text fusion-title-size-two\" style=\"--awb-margin-bottom-small:8px;--awb-font-size:30px;\"><h2 class=\"fusion-title-heading title-heading-left fusion-responsive-typography-calculated\" style=\"font-family:&quot;PT Serif&quot;;font-style:normal;font-weight:700;margin:0;letter-spacing:1.6xp;font-size:1em;--fontSize:30;line-height:1.47;\">The dataset : sales receipts<\/h2><\/div><div class=\"fusion-text fusion-text-8\"><p id=\"ember57\">We will use the TaFeng grocery dataset. You can download it from\u00a0<a href=\"https:\/\/www.kaggle.com\/datasets\/chiranjivdas09\/ta-feng-grocery-dataset\" target=\"_blank\" rel=\"noopener ugc nofollow\">Kaggle<\/a>\u00a0and open it in your Python environment with choice-learn:<\/p>\n<div class=\"code\">from choice_learn.datasets import load_tafeng<\/div>\n<div class=\"code\">tafeng_df = load_tafeng(as_frame=True)<br \/>\nprint(tafeng_df.head())<\/div>\n<\/div><div class=\"fusion-image-element\" style=\"--awb-caption-title-font-family:var(--h2_typography-font-family);--awb-caption-title-font-weight:var(--h2_typography-font-weight);--awb-caption-title-font-style:var(--h2_typography-font-style);--awb-caption-title-size:var(--h2_typography-font-size);--awb-caption-title-transform:var(--h2_typography-text-transform);--awb-caption-title-line-height:var(--h2_typography-line-height);--awb-caption-title-letter-spacing:var(--h2_typography-letter-spacing);\"><span class=\" fusion-imageframe imageframe-none imageframe-2 hover-type-none\"><a class=\"fusion-no-lightbox\" href=\"https:\/\/medium.com\/artefact-engineering-and-data-science\/assortment-optimization-with-discrete-choice-models-in-python-2efc4d9a4aba\" target=\"_self\" aria-label=\"Capture d\u2019e\u0301cran 2025-02-18 a\u0300 17.04.53\" rel=\"noopener\"><img decoding=\"async\" width=\"1024\" height=\"153\" src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.04.53.png\" data-orig-src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.04.53-1024x153.png\" alt class=\"lazyload img-responsive wp-image-333449\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%271354%27%20height%3D%27202%27%20viewBox%3D%270%200%201354%20202%27%3E%3Crect%20width%3D%271354%27%20height%3D%27202%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.04.53-200x30.png 200w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.04.53-400x60.png 400w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.04.53-600x90.png 600w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.04.53-800x119.png 800w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.04.53-1200x179.png 1200w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.04.53.png 1354w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 640px) 100vw, 1024px\" \/><\/a><\/span><\/div><div class=\"fusion-text fusion-text-9\"><p id=\"76f9\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">The dataset consists of over 800,000 individual purchases in a Chinese grocery store. For each purchase, various details are provided, including the purchased item (PRODUCT_ID), the price at which it was sold (SALES_PRICE), and the customer\u2019s age group (AGE_GROUP).<\/p>\n<p id=\"91eb\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">You can observe that many different items are provided and some of them are seldom sold. In order to streamline logistics, the retailer may choose to reduce the number of products they offer. The goal in this case is to identify the optimal subset of items to sell.<\/p>\n<p id=\"54ca\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">To achieve this, we focus on the top-selling items, as they are more likely to be purchased again and will play a crucial role in shaping a more efficient and profitable assortment.\u00a0<em class=\"ns\">Note that we do this mainly to simplify the example and that all items could be kept.<\/em><\/p>\n<div class=\"code\"><span class=\"hljs-comment\"># Keep only top 20 selling products<\/span><br \/>\ntafeng_df = tafeng_df.loc[<br \/>\ntafeng_df.PRODUCT_ID.isin(tafeng_df.PRODUCT_ID.value_counts().index[:<span class=\"hljs-number\">20<\/span>])<br \/>\n].reset_index(drop=<span class=\"hljs-literal\">True<\/span>)<\/div>\n<div class=\"code\"><span class=\"hljs-comment\"># Remove NaN values<\/span><br \/>\ntafeng_df = tafeng_df.loc[<br \/>\ntafeng_df.AGE_GROUP.isin([<span class=\"hljs-string\">&#8220;25-29&#8221;<\/span>, <span class=\"hljs-string\">&#8220;40-44&#8221;<\/span>, <span class=\"hljs-string\">&#8220;45-49&#8221;<\/span>, <span class=\"hljs-string\">&#8220;&gt;65&#8221;<\/span>, <span class=\"hljs-string\">&#8220;30-34&#8221;<\/span>, <span class=\"hljs-string\">&#8220;35-39&#8221;<\/span>, <span class=\"hljs-string\">&#8220;50-54&#8221;<\/span>, <span class=\"hljs-string\">&#8220;55-59&#8221;<\/span>, <span class=\"hljs-string\">&#8220;60-64&#8221;<\/span>]\n)<br \/>\n].reset_index(drop=<span class=\"hljs-literal\">True<\/span>)<\/div>\n<div class=\"code\"><span class=\"hljs-built_in\">print<\/span>(tafeng_df.head())<\/div>\n<\/div><div class=\"fusion-text fusion-text-10\"><p id=\"76f9\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">Let\u2019s also encode the age categories with one hot values every ten year:<\/p>\n<div class=\"code\"><span class=\"hljs-comment\"># Encoding the age categories<\/span><br \/>\ntafeng_df[<span class=\"hljs-string\">&#8220;twenties&#8221;<\/span>] = tafeng_df.apply(<span class=\"hljs-keyword\">lambda<\/span> row: <span class=\"hljs-number\">1<\/span> <span class=\"hljs-keyword\">if<\/span> row[<span class=\"hljs-string\">&#8220;AGE_GROUP&#8221;<\/span>] == <span class=\"hljs-string\">&#8220;25-29&#8221;<\/span> <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-number\">0<\/span>, axis=<span class=\"hljs-number\">1<\/span>)<br \/>\ntafeng_df[<span class=\"hljs-string\">&#8220;thirties&#8221;<\/span>] = tafeng_df.apply(<br \/>\n<span class=\"hljs-keyword\">lambda<\/span> row: <span class=\"hljs-number\">1<\/span> <span class=\"hljs-keyword\">if<\/span> row[<span class=\"hljs-string\">&#8220;AGE_GROUP&#8221;<\/span>] <span class=\"hljs-keyword\">in<\/span> ([<span class=\"hljs-string\">&#8220;30-34&#8221;<\/span>, <span class=\"hljs-string\">&#8220;35-39&#8221;<\/span>]) <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-number\">0<\/span>, axis=<span class=\"hljs-number\">1<\/span><br \/>\n)<br \/>\ntafeng_df[<span class=\"hljs-string\">&#8220;forties&#8221;<\/span>] = tafeng_df.apply(<br \/>\n<span class=\"hljs-keyword\">lambda<\/span> row: <span class=\"hljs-number\">1<\/span> <span class=\"hljs-keyword\">if<\/span> row[<span class=\"hljs-string\">&#8220;AGE_GROUP&#8221;<\/span>] <span class=\"hljs-keyword\">in<\/span> ([<span class=\"hljs-string\">&#8220;40-44&#8221;<\/span>, <span class=\"hljs-string\">&#8220;45-49&#8221;<\/span>]) <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-number\">0<\/span>, axis=<span class=\"hljs-number\">1<\/span><br \/>\n)<br \/>\ntafeng_df[<span class=\"hljs-string\">&#8220;fifties&#8221;<\/span>] = tafeng_df.apply(<br \/>\n<span class=\"hljs-keyword\">lambda<\/span> row: <span class=\"hljs-number\">1<\/span> <span class=\"hljs-keyword\">if<\/span> row[<span class=\"hljs-string\">&#8220;AGE_GROUP&#8221;<\/span>] <span class=\"hljs-keyword\">in<\/span> ([<span class=\"hljs-string\">&#8220;50-54&#8221;<\/span>, <span class=\"hljs-string\">&#8220;55-59&#8221;<\/span>]) <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-number\">0<\/span>, axis=<span class=\"hljs-number\">1<\/span><br \/>\n)<br \/>\ntafeng_df[<span class=\"hljs-string\">&#8220;sixties_and_above&#8221;<\/span>] = tafeng_df.apply(<br \/>\n<span class=\"hljs-keyword\">lambda<\/span> row: <span class=\"hljs-number\">1<\/span> <span class=\"hljs-keyword\">if<\/span> row[<span class=\"hljs-string\">&#8220;AGE_GROUP&#8221;<\/span>] <span class=\"hljs-keyword\">in<\/span> ([<span class=\"hljs-string\">&#8220;60-64&#8221;<\/span>, <span class=\"hljs-string\">&#8220;&gt;65&#8221;<\/span>]) <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-number\">0<\/span>, axis=<span class=\"hljs-number\">1<\/span><br \/>\n)<\/div>\n<\/div><div class=\"fusion-text fusion-text-11\"><p id=\"04da\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">Now that our data is ready, we need to create a\u00a0<strong class=\"mw gv\">ChoiceDataset<\/strong>, the data handler object in\u00a0<strong class=\"mw gv\">choice-learn<\/strong>. This involves specifying the features that describe the context in which a purchase is made:<\/p>\n<ul class=\"\">\n<li id=\"cc92\" class=\"mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr pz qa qb bk\" data-selectable-paragraph=\"\"><strong class=\"mw gv\">Customer characteristics<\/strong>\u00a0(shared features): the age category<\/li>\n<li id=\"d6d9\" class=\"mu mv gu mw b mx qc mz na nb qd nd ne nf qe nh ni nj qf nl nm nn qg np nq nr pz qa qb bk\" data-selectable-paragraph=\"\"><strong class=\"mw gv\">Product characteristics<\/strong>\u00a0(item features): the item price<\/li>\n<\/ul>\n<p id=\"f16b\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">A key aspect of choice modeling is that we require the characteristics of\u00a0<strong class=\"mw gv\">all available items at the time of a purchase<\/strong>, not just the chosen one. This allows us to analyze how the prices of different products influence the customer\u2019s decision. Since this information isn\u2019t directly available in the dataset, we make the assumption that for each purchase, the prices of the other items remain the same as they were in the previous sale.<\/p>\n<\/div><div class=\"fusion-text fusion-text-12\"><div class=\"code\"><span class=\"hljs-comment\"># product ID to index<\/span><br \/>\nid_to_index = <br \/>\n<span class=\"hljs-keyword\">for<\/span> i, product_id <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-built_in\">enumerate<\/span>(np.sort(tafeng_df.PRODUCT_ID.unique())):<br \/>\nid_to_index[product_id] = i<br \/>\n<span class=\"hljs-comment\"># Initialize the items price<\/span><br \/>\nprices = [[<span class=\"hljs-number\">0<\/span>] <span class=\"hljs-keyword\">for<\/span> _ <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-built_in\">range<\/span>(<span class=\"hljs-built_in\">len<\/span>(id_to_index))]\n<span class=\"hljs-keyword\">for<\/span> k, v <span class=\"hljs-keyword\">in<\/span> id_to_index.items():<br \/>\nprices[v][<span class=\"hljs-number\">0<\/span>] = tafeng_df.loc[tafeng_df.PRODUCT_ID == k].SALES_PRICE.to_numpy()[<span class=\"hljs-number\">0<\/span>]\n<span class=\"hljs-comment\"># Create the arrays that will constitute the ChoiceDataset<\/span><br \/>\nshared_features = []\nitems_features = []\nchoices = []\n<span class=\"hljs-comment\"># For each bought item, we save:<\/span><br \/>\n<span class=\"hljs-comment\"># \u2013 the age representation (one-hot) of the customer<\/span><br \/>\n<span class=\"hljs-comment\"># \u2013 the price of all sold items<\/span><br \/>\n<span class=\"hljs-keyword\">for<\/span> i, row <span class=\"hljs-keyword\">in<\/span> tafeng_df.iterrows():<br \/>\nitem_index = id_to_index[row.PRODUCT_ID]\nprices[item_index][<span class=\"hljs-number\">0<\/span>] = row.SALES_PRICE<br \/>\nshared_features.append(<br \/>\nrow[[<span class=\"hljs-string\">\"twenties\"<\/span>, <span class=\"hljs-string\">\"thirties\"<\/span>, <span class=\"hljs-string\">\"forties\"<\/span>, <span class=\"hljs-string\">\"fifties\"<\/span>, <span class=\"hljs-string\">\"sixties_and_above\"<\/span>]].to_numpy()<br \/>\n)<br \/>\nitems_features.append(prices)<br \/>\nchoices.append(item_index)<\/div>\n<\/div><div class=\"fusion-text fusion-text-13\"><p>Now that we have all our information, we can\u00a0<strong class=\"mw gv\">create the ChoiceDataset<\/strong>:<\/p>\n<\/div><div class=\"fusion-text fusion-text-14\"><div class=\"code\"><span class=\"hljs-keyword\">from<\/span> choice_learn.data <span class=\"hljs-keyword\">import<\/span> ChoiceDataset<br \/>\ndataset = ChoiceDataset(<br \/>\nshared_features_by_choice=shared_features,<br \/>\nshared_features_by_choice_names=[<span class=\"hljs-string\">&#8216;twenties&#8217;<\/span>, <span class=\"hljs-string\">&#8216;thirties&#8217;<\/span>, <span class=\"hljs-string\">&#8216;forties&#8217;<\/span>, <span class=\"hljs-string\">&#8216;fifties&#8217;<\/span>, <span class=\"hljs-string\">&#8216;sixties_and_above&#8217;<\/span>],<br \/>\nitems_features_by_choice=items_features,<br \/>\nitems_features_by_choice_names=[<span class=\"hljs-string\">&#8220;SALES_PRICE&#8221;<\/span>],<br \/>\nchoices=choices<br \/>\n)<\/div>\n<\/div><div class=\"fusion-title title fusion-title-5 fusion-sep-none fusion-title-text fusion-title-size-two\" style=\"--awb-margin-bottom-small:8px;--awb-font-size:30px;\"><h2 class=\"fusion-title-heading title-heading-left fusion-responsive-typography-calculated\" style=\"font-family:&quot;PT Serif&quot;;font-style:normal;font-weight:700;margin:0;letter-spacing:1.6xp;font-size:1em;--fontSize:30;line-height:1.47;\">Defining and estimating the choice model<\/h2><\/div><div class=\"fusion-text fusion-text-15\"><p id=\"0cb5\" class=\"pw-post-body-paragraph mu mv gu mw b mx pk mz na nb pl nd ne nf pm nh ni nj pn nl nm nn po np nq nr gn bk\" data-selectable-paragraph=\"\">We will develop and estimate a choice model that predicts the probability of a customer selecting a specific item from an entire assortment of similar products. Based on the available dataset, we define the following utility function for an item\u00a0<em class=\"ns\">i<\/em>\u00a0considered by a customer\u00a0<em class=\"ns\">j:<\/em><\/p>\n<figure class=\"nw nx ny nz oa ob nt nu paragraph-image\">\n<div class=\"oc od fj oe bh of\" tabindex=\"0\" role=\"button\"><\/div>\n<\/figure>\n<\/div><div class=\"fusion-image-element\" style=\"--awb-caption-title-font-family:var(--h2_typography-font-family);--awb-caption-title-font-weight:var(--h2_typography-font-weight);--awb-caption-title-font-style:var(--h2_typography-font-style);--awb-caption-title-size:var(--h2_typography-font-size);--awb-caption-title-transform:var(--h2_typography-text-transform);--awb-caption-title-line-height:var(--h2_typography-line-height);--awb-caption-title-letter-spacing:var(--h2_typography-letter-spacing);\"><span class=\" fusion-imageframe imageframe-none imageframe-3 hover-type-none\"><a class=\"fusion-no-lightbox\" href=\"https:\/\/medium.com\/artefact-engineering-and-data-science\/assortment-optimization-with-discrete-choice-models-in-python-2efc4d9a4aba\" target=\"_self\" aria-label=\"1_o84G0YQyU59jPIACslbhxw (1)\" rel=\"noopener\"><img decoding=\"async\" width=\"880\" height=\"114\" src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_o84G0YQyU59jPIACslbhxw-1.webp\" data-orig-src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_o84G0YQyU59jPIACslbhxw-1.webp\" alt class=\"lazyload img-responsive wp-image-333454\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27880%27%20height%3D%27114%27%20viewBox%3D%270%200%20880%20114%27%3E%3Crect%20width%3D%27880%27%20height%3D%27114%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_o84G0YQyU59jPIACslbhxw-1-200x26.webp 200w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_o84G0YQyU59jPIACslbhxw-1-400x52.webp 400w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_o84G0YQyU59jPIACslbhxw-1-600x78.webp 600w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_o84G0YQyU59jPIACslbhxw-1-800x104.webp 800w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_o84G0YQyU59jPIACslbhxw-1.webp 880w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 640px) 100vw, 880px\" \/><\/a><\/span><\/div><div class=\"fusion-text fusion-text-16\"><p id=\"9d97\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">This function represents the utility (or satisfaction) a customer derives from choosing a particular item, influenced by both the customer\u2019s age and the item\u2019s price.<\/p>\n<p id=\"6d2e\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">For more details on how we formulate a utility function, refer to our first\u00a0<a class=\"af ol\" href=\"https:\/\/medium.com\/artefact-engineering-and-data-science\/modeling-customers-decisions-in-python-with-the-choice-learn-package-37752cb7932e\" rel=\"noopener\" target=\"_blank\">post<\/a>. Note that another logical \u2014 but not presented to keep it simple \u2014 model could be to estimate one price sensitivity per age category.<\/p>\n<p id=\"f9be\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">Here is the code to estimate such a model with choice-learn:<\/p>\n<\/div><div class=\"fusion-text fusion-text-17\"><div class=\"code\"><span class=\"hljs-keyword\">from<\/span> choice_learn.models <span class=\"hljs-keyword\">import<\/span> ConditionalLogit<\/div>\n<div class=\"code\">model = ConditionalLogit(optimizer=<span class=\"hljs-string\">&#8220;Adam&#8221;<\/span>, batch_size=<span class=\"hljs-number\">1024<\/span>, epochs=<span class=\"hljs-number\">300<\/span>, lr=<span class=\"hljs-number\">0.002<\/span>)<\/div>\n<div class=\"code\"><span class=\"hljs-keyword\">for<\/span> age_category <span class=\"hljs-keyword\">in<\/span> [<span class=\"hljs-string\">&#8220;twenties&#8221;<\/span>, <span class=\"hljs-string\">&#8220;thirties&#8221;<\/span>, <span class=\"hljs-string\">&#8220;forties&#8221;<\/span>, <span class=\"hljs-string\">&#8220;fifties&#8221;<\/span>, <span class=\"hljs-string\">&#8220;sixties_and_above&#8221;<\/span>]:<br \/>\nmodel.add_coefficients(<br \/>\ncoefficient_name=age_category, feature_name=age_category, items_indexes=<span class=\"hljs-built_in\">list<\/span>(<span class=\"hljs-built_in\">range<\/span>(<span class=\"hljs-number\">20<\/span>))<br \/>\n)<\/div>\n<div class=\"code\">model.add_shared_coefficient(<br \/>\ncoefficient_name=<span class=\"hljs-string\">&#8220;price&#8221;<\/span>, feature_name=<span class=\"hljs-string\">&#8220;SALES_PRICE&#8221;<\/span>, items_indexes=<span class=\"hljs-built_in\">list<\/span>(<span class=\"hljs-built_in\">range<\/span>(<span class=\"hljs-number\">20<\/span>))<br \/>\n)<\/div>\n<div class=\"code\">hist = model.fit(dataset)<\/div>\n<\/div><div class=\"fusion-text fusion-text-18\"><p id=\"9d97\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">You can check that the model does fit well on the dataset:<\/p>\n<\/div><div class=\"fusion-text fusion-text-19\"><div class=\"code\"><span class=\"hljs-keyword\">import<\/span> matplotlib.pyplot <span class=\"hljs-keyword\">as<\/span> plt<br \/>\nplt.plot(hist[<span class=\"hljs-string\">&#8220;train_loss&#8221;<\/span>])<br \/>\nplt.xlabel(<span class=\"hljs-string\">&#8220;Epoch&#8221;<\/span>)<br \/>\nplt.ylabel(<span class=\"hljs-string\">&#8220;Negative Log Likelihood&#8221;<\/span>)<br \/>\nplt.show(<\/div>\n<\/div><div class=\"fusion-image-element\" style=\"--awb-caption-title-font-family:var(--h2_typography-font-family);--awb-caption-title-font-weight:var(--h2_typography-font-weight);--awb-caption-title-font-style:var(--h2_typography-font-style);--awb-caption-title-size:var(--h2_typography-font-size);--awb-caption-title-transform:var(--h2_typography-text-transform);--awb-caption-title-line-height:var(--h2_typography-line-height);--awb-caption-title-letter-spacing:var(--h2_typography-letter-spacing);\"><span class=\" fusion-imageframe imageframe-none imageframe-4 hover-type-none\"><a class=\"fusion-no-lightbox\" href=\"https:\/\/medium.com\/artefact-engineering-and-data-science\/assortment-optimization-with-discrete-choice-models-in-python-2efc4d9a4aba\" target=\"_self\" aria-label=\"Capture d\u2019e\u0301cran 2025-02-18 a\u0300 17.20.12\" rel=\"noopener\"><img decoding=\"async\" width=\"1024\" height=\"678\" src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.20.12.png\" data-orig-src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.20.12-1024x678.png\" alt class=\"lazyload img-responsive wp-image-333457\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%271432%27%20height%3D%27948%27%20viewBox%3D%270%200%201432%20948%27%3E%3Crect%20width%3D%271432%27%20height%3D%27948%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.20.12-200x132.png 200w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.20.12-400x265.png 400w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.20.12-600x397.png 600w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.20.12-800x530.png 800w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.20.12-1200x794.png 1200w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.20.12.png 1432w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 640px) 100vw, 1024px\" \/><\/a><\/span><\/div><div class=\"fusion-title title fusion-title-6 fusion-sep-none fusion-title-text fusion-title-size-two\" style=\"--awb-margin-bottom-small:8px;--awb-font-size:30px;\"><h2 class=\"fusion-title-heading title-heading-left fusion-responsive-typography-calculated\" style=\"font-family:&quot;PT Serif&quot;;font-style:normal;font-weight:700;margin:0;letter-spacing:1.6xp;font-size:1em;--fontSize:30;line-height:1.47;\">Finding the optimal assortment<\/h2><\/div><div class=\"fusion-text fusion-text-20\"><p id=\"9d97\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">With the purchase probabilities in hand, we can now estimate the average revenue per customer of an assortment\u00a0<em class=\"ns\">A\u00a0<\/em>using the formula:<\/p>\n<\/div><div class=\"fusion-image-element\" style=\"--awb-caption-title-font-family:var(--h2_typography-font-family);--awb-caption-title-font-weight:var(--h2_typography-font-weight);--awb-caption-title-font-style:var(--h2_typography-font-style);--awb-caption-title-size:var(--h2_typography-font-size);--awb-caption-title-transform:var(--h2_typography-text-transform);--awb-caption-title-line-height:var(--h2_typography-line-height);--awb-caption-title-letter-spacing:var(--h2_typography-letter-spacing);\"><span class=\" fusion-imageframe imageframe-none imageframe-5 hover-type-none\"><a class=\"fusion-no-lightbox\" href=\"https:\/\/medium.com\/artefact-engineering-and-data-science\/assortment-optimization-with-discrete-choice-models-in-python-2efc4d9a4aba\" target=\"_self\" aria-label=\"1_qsmQIrhpjHOhDpDMLWvXZQ (1)\" rel=\"noopener\"><img decoding=\"async\" width=\"902\" height=\"146\" src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_qsmQIrhpjHOhDpDMLWvXZQ-1.webp\" data-orig-src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_qsmQIrhpjHOhDpDMLWvXZQ-1.webp\" alt class=\"lazyload img-responsive wp-image-333458\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27902%27%20height%3D%27146%27%20viewBox%3D%270%200%20902%20146%27%3E%3Crect%20width%3D%27902%27%20height%3D%27146%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_qsmQIrhpjHOhDpDMLWvXZQ-1-200x32.webp 200w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_qsmQIrhpjHOhDpDMLWvXZQ-1-400x65.webp 400w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_qsmQIrhpjHOhDpDMLWvXZQ-1-600x97.webp 600w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_qsmQIrhpjHOhDpDMLWvXZQ-1-800x129.webp 800w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/1_qsmQIrhpjHOhDpDMLWvXZQ-1.webp 902w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 640px) 100vw, 902px\" \/><\/a><\/span><\/div><div class=\"fusion-text fusion-text-21\"><p id=\"f2aa\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">To find the assortment that maximizes revenue, we could evaluate all possible combinations and select the one with the highest average revenue. However, a more efficient approach is to use<a class=\"af ol\" href=\"https:\/\/en.wikipedia.org\/wiki\/Linear_programming\" target=\"_blank\" rel=\"noopener ugc nofollow\">\u00a0<strong class=\"mw gv\">Linear Programming (LP)<\/strong><\/a>. Here, we\u2019ll focus on how to use the\u00a0<strong class=\"mw gv\">choice-learn<\/strong>\u00a0implementation of the assortment optimizer.<\/p>\n<p id=\"1c6c\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">It\u2019s important to distinguish between maximizing revenue and maximizing profit margins. While revenue is important, profit margins take into account the costs associated with each product. Depending on your goal, you may want to optimize for profit rather than pure revenue.<\/p>\n<p id=\"0cca\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">To optimize the assortment, we need to provide several key inputs:<\/p>\n<ul class=\"\">\n<li id=\"73cb\" class=\"mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr pz qa qb bk\" data-selectable-paragraph=\"\">The weight we want to give to each age category, let\u2019s go with their customer share<\/li>\n<li id=\"1b1a\" class=\"mu mv gu mw b mx qc mz na nb qd nd ne nf qe nh ni nj qf nl nm nn qg np nq nr pz qa qb bk\" data-selectable-paragraph=\"\">The utility of each item (calculated by our choice model) for each age category<\/li>\n<li id=\"97e3\" class=\"mu mv gu mw b mx qc mz na nb qd nd ne nf qe nh ni nj qf nl nm nn qg np nq nr pz qa qb bk\" data-selectable-paragraph=\"\">The value to optimize for each item (in this case, revenue)<\/li>\n<li id=\"70b5\" class=\"mu mv gu mw b mx qc mz na nb qd nd ne nf qe nh ni nj qf nl nm nn qg np nq nr pz qa qb bk\" data-selectable-paragraph=\"\">The size of the assortment (for example, 12 items)<\/li>\n<\/ul>\n<p id=\"725a\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">Here\u2019s how it works using\u00a0<strong class=\"mw gv\">choice-learn<\/strong>:<\/p>\n<pre class=\"nw nx ny nz oa pp pq pr bp ps bb bk\"><\/pre>\n<\/div><div class=\"fusion-text fusion-text-22\"><div id=\"725a\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk code\">\n<pre class=\"nw nx ny nz oa pp pq pr bp ps bb bk\"><span class=\"pt on gu pq b bg pu pv l pw px\" data-selectable-paragraph=\"\"><span class=\"hljs-keyword\">from<\/span> choice_learn.toolbox.assortment_optimizer <span class=\"hljs-keyword\">import<\/span> LatentClassAssortmentOptimizer\r\n<\/span><span class=\"hljs-comment\"># Price of each item<\/span>\r\nfuture_prices = np.stack([items_features[-<span class=\"hljs-number\">1<\/span>]]*<span class=\"hljs-number\">5<\/span>, axis=<span class=\"hljs-number\">0<\/span>)\r\nage_category = np.eye(<span class=\"hljs-number\">5<\/span>).astype(<span class=\"hljs-string\">\"float32\"<\/span>)\r\n<span class=\"hljs-comment\"># Compute utility of each item given its price and each age category<\/span>\r\npredicted_utilities = model.compute_batch_utility(shared_features_by_choice=age_category,\r\n                                                  items_features_by_choice=future_prices,\r\n                                                  available_items_by_choice=np.ones((<span class=\"hljs-number\">5<\/span>, <span class=\"hljs-number\">20<\/span>)),\r\n                                                  choices=<span class=\"hljs-literal\">None<\/span>\r\n                                                  )\r\nage_category_weights = np.<span class=\"hljs-built_in\">sum<\/span>(shared_features, axis=<span class=\"hljs-number\">0<\/span>) \/ <span class=\"hljs-built_in\">len<\/span>(shared_features)\r\nopt = LatentClassAssortmentOptimizer(\r\nsolver=<span class=\"hljs-string\">\"or-tools\"<\/span>, <span class=\"hljs-comment\"># Solver to use, either \"or-tools\" or \"gurobi\" (if you have a license)<\/span>\r\nclass_weights=age_category_weights, <span class=\"hljs-comment\"># Weights of each class<\/span>\r\nclass_utilities=np.exp(predicted_utilities), <span class=\"hljs-comment\"># utilities in the shape (n_classes, n_items)<\/span>\r\nitemwise_values=future_prices[<span class=\"hljs-number\">0<\/span>][:, <span class=\"hljs-number\">0<\/span>], <span class=\"hljs-comment\"># Values to optimize for each item, here price that is used to compute turnover<\/span>\r\nassortment_size=<span class=\"hljs-number\">12<\/span>) <span class=\"hljs-comment\"># Size of the assortment we want\r\n<\/span>assortment, opt_obj = opt.solve()<\/pre>\n<\/div>\n<pre class=\"nw nx ny nz oa pp pq pr bp ps bb bk\"><\/pre>\n<\/div><div class=\"fusion-text fusion-text-23\"><p id=\"725a\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">Running the code you should have something like:<\/p>\n<pre class=\"nw nx ny nz oa pp pq pr bp ps bb bk\"><\/pre>\n<\/div><div class=\"fusion-image-element\" style=\"--awb-caption-title-font-family:var(--h2_typography-font-family);--awb-caption-title-font-weight:var(--h2_typography-font-weight);--awb-caption-title-font-style:var(--h2_typography-font-style);--awb-caption-title-size:var(--h2_typography-font-size);--awb-caption-title-transform:var(--h2_typography-text-transform);--awb-caption-title-line-height:var(--h2_typography-line-height);--awb-caption-title-letter-spacing:var(--h2_typography-letter-spacing);\"><span class=\" fusion-imageframe imageframe-none imageframe-6 hover-type-none\"><a class=\"fusion-no-lightbox\" href=\"https:\/\/medium.com\/artefact-engineering-and-data-science\/assortment-optimization-with-discrete-choice-models-in-python-2efc4d9a4aba\" target=\"_self\" aria-label=\"Capture d\u2019e\u0301cran 2025-02-18 a\u0300 17.24.46\" rel=\"noopener\"><img decoding=\"async\" width=\"1024\" height=\"273\" src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.24.46.png\" data-orig-src=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.24.46-1024x273.png\" alt class=\"lazyload img-responsive wp-image-333460\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%271352%27%20height%3D%27360%27%20viewBox%3D%270%200%201352%20360%27%3E%3Crect%20width%3D%271352%27%20height%3D%27360%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.24.46-200x53.png 200w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.24.46-400x107.png 400w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.24.46-600x160.png 600w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.24.46-800x213.png 800w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.24.46-1200x320.png 1200w, https:\/\/www.artefact.com\/\/wp-content\/uploads\/2025\/02\/Capture-decran-2025-02-18-a-17.24.46.png 1352w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 640px) 100vw, 1024px\" \/><\/a><\/span><\/div><div class=\"fusion-text fusion-text-24\"><p id=\"e9c4\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">The optimal assortment for maximizing revenue is indicated with the indexes of the 1 values in the vector. This assortment theoretically yields an average revenue per customer of 134 yuan. You can explore other combinations, but they will all result in lower average revenue.<\/p>\n<p id=\"742d\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">Another objective could be to maximize the number of sales. In this scenario, the item-wise value for optimization is set to 1 for all items, leading to a different optimal assortment.<\/p>\n<p id=\"10d3\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">The efficiency of this method becomes evident when additional constraints are introduced. For instance, you may need to account for shelf space limitations in your store. In this case, you can optimize for an assortment whose total item size does not exceed the available shelf space. This additional constraint, along with others such as pricing strategies, is demonstrated\u00a0<a class=\"af ol\" href=\"https:\/\/github.com\/artefactory\/choice-learn\/blob\/2ec81cb091951edb3dbbc4d601f7e8fb5e5700dd\/notebooks\/auxiliary_tools\/assortment_example.ipynb\" target=\"_blank\" rel=\"noopener ugc nofollow\">here<\/a>.<\/p>\n<pre class=\"nw nx ny nz oa pp pq pr bp ps bb bk\"><\/pre>\n<\/div><div class=\"fusion-title title fusion-title-7 fusion-sep-none fusion-title-text fusion-title-size-two\" style=\"--awb-margin-bottom-small:8px;--awb-font-size:30px;\"><h2 class=\"fusion-title-heading title-heading-left fusion-responsive-typography-calculated\" style=\"font-family:&quot;PT Serif&quot;;font-style:normal;font-weight:700;margin:0;letter-spacing:1.6xp;font-size:1em;--fontSize:30;line-height:1.47;\">Conclusion<\/h2><\/div><div class=\"fusion-text fusion-text-25\"><p id=\"e9c4\" class=\"pw-post-body-paragraph mu mv gu mw b mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr gn bk\" data-selectable-paragraph=\"\">If you are working on assortment optimization or pricing, choice modeling is a great tool, be sure to look into it. Choice-Learn provides many cool examples on its\u00a0<a class=\"af ol\" href=\"https:\/\/github.com\/artefactory\/choice-learn\" target=\"_blank\" rel=\"noopener ugc nofollow\">GitHub<\/a>. Go check it out and leave as star if you find it useful !<\/p>\n<\/div><\/div><\/div><\/div><\/article><\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 prioriteit moeten krijgen op basis van klantvoorkeuren, seizoensgebonden trends en verkooppatronen.<\/p>","protected":false},"featured_media":333322,"parent":0,"template":"","meta":{"_acf_changed":false,"ep_exclude_from_search":false},"blog-category":[21939],"blog-language":[2991,2993],"class_list":["post-333207","blog","type-blog","status-publish","has-post-thumbnail","hentry","blog-category-medium","blog-language-en","blog-language-fr"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.artefact.com\/nl\/wp-json\/wp\/v2\/blog\/333207","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.artefact.com\/nl\/wp-json\/wp\/v2\/blog"}],"about":[{"href":"https:\/\/www.artefact.com\/nl\/wp-json\/wp\/v2\/types\/blog"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.artefact.com\/nl\/wp-json\/wp\/v2\/media\/333322"}],"wp:attachment":[{"href":"https:\/\/www.artefact.com\/nl\/wp-json\/wp\/v2\/media?parent=333207"}],"wp:term":[{"taxonomy":"blog-category","embeddable":true,"href":"https:\/\/www.artefact.com\/nl\/wp-json\/wp\/v2\/blog-category?post=333207"},{"taxonomy":"blog-language","embeddable":true,"href":"https:\/\/www.artefact.com\/nl\/wp-json\/wp\/v2\/blog-language?post=333207"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}