Palplatine commited on
Commit
604e735
·
1 Parent(s): 41261b8

Adding files segmentation project

Browse files
1_customer_segmentation_eda.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
2_customer_segmentation_models.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
3_customer_segmentation_update_frequency.ipynb ADDED
@@ -0,0 +1,975 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "attachments": {},
5
+ "cell_type": "markdown",
6
+ "metadata": {},
7
+ "source": [
8
+ "Our goal here is to cluster our customers once again and check how our clusters change weeks after weeks. This will enable us to see when we will need to update our algorithm.\n",
9
+ "\n",
10
+ "To do this, we will calculate the Adjusted Rand Index between a cluster made with out original data preprocessing and a cluster made with a new preprocessing taking into account the new data. Then, we will be able to see the evolution of the ARI weeks after weeks and see when an update is necessary."
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": 1,
16
+ "metadata": {},
17
+ "outputs": [],
18
+ "source": [
19
+ "# Importing packages\n",
20
+ "import pandas as pd\n",
21
+ "import numpy as np\n",
22
+ "import matplotlib.pyplot as plt\n",
23
+ "import seaborn as sns\n",
24
+ "from tqdm import tqdm\n",
25
+ "\n",
26
+ "from datetime import date\n",
27
+ "from datetime import timedelta\n",
28
+ "\n",
29
+ "from sklearn.preprocessing import StandardScaler\n",
30
+ "from sklearn.cluster import KMeans\n",
31
+ "from sklearn.metrics.cluster import adjusted_rand_score\n",
32
+ "\n",
33
+ "from yellowbrick.cluster import KElbowVisualizer\n",
34
+ "from yellowbrick.cluster import SilhouetteVisualizer"
35
+ ]
36
+ },
37
+ {
38
+ "cell_type": "code",
39
+ "execution_count": 2,
40
+ "metadata": {},
41
+ "outputs": [],
42
+ "source": [
43
+ "# To remove some warnings (setting with a copy)\n",
44
+ "pd.options.mode.chained_assignment = None"
45
+ ]
46
+ },
47
+ {
48
+ "attachments": {},
49
+ "cell_type": "markdown",
50
+ "metadata": {},
51
+ "source": [
52
+ "We will be using all our available data (so UK and the other countries)."
53
+ ]
54
+ },
55
+ {
56
+ "cell_type": "code",
57
+ "execution_count": 3,
58
+ "metadata": {},
59
+ "outputs": [],
60
+ "source": [
61
+ "# Loading data\n",
62
+ "df = pd.read_csv('static/customer_segmentation.csv', encoding='latin1')"
63
+ ]
64
+ },
65
+ {
66
+ "attachments": {},
67
+ "cell_type": "markdown",
68
+ "metadata": {},
69
+ "source": [
70
+ "We change our InvoiceDate column into a datetime column."
71
+ ]
72
+ },
73
+ {
74
+ "cell_type": "code",
75
+ "execution_count": 4,
76
+ "metadata": {},
77
+ "outputs": [],
78
+ "source": [
79
+ "df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'])\n",
80
+ "df['InvoiceDate'] = df['InvoiceDate'].dt.to_period('D')\n",
81
+ "df['InvoiceDate'] = df['InvoiceDate'].astype('datetime64[ns]')"
82
+ ]
83
+ },
84
+ {
85
+ "cell_type": "code",
86
+ "execution_count": 5,
87
+ "metadata": {},
88
+ "outputs": [],
89
+ "source": [
90
+ "# We remove missing rows and columns we won't use in this analysis\n",
91
+ "df = df[df['CustomerID'].notnull()]\n",
92
+ "df = df.reset_index()\n",
93
+ "df = df.drop(['StockCode', 'Country', 'index'], axis=1)"
94
+ ]
95
+ },
96
+ {
97
+ "attachments": {},
98
+ "cell_type": "markdown",
99
+ "metadata": {},
100
+ "source": [
101
+ "We remove the order amounts that have a negative value (discounts or returned orders)."
102
+ ]
103
+ },
104
+ {
105
+ "cell_type": "code",
106
+ "execution_count": 6,
107
+ "metadata": {},
108
+ "outputs": [
109
+ {
110
+ "data": {
111
+ "text/html": [
112
+ "<div>\n",
113
+ "<style scoped>\n",
114
+ " .dataframe tbody tr th:only-of-type {\n",
115
+ " vertical-align: middle;\n",
116
+ " }\n",
117
+ "\n",
118
+ " .dataframe tbody tr th {\n",
119
+ " vertical-align: top;\n",
120
+ " }\n",
121
+ "\n",
122
+ " .dataframe thead th {\n",
123
+ " text-align: right;\n",
124
+ " }\n",
125
+ "</style>\n",
126
+ "<table border=\"1\" class=\"dataframe\">\n",
127
+ " <thead>\n",
128
+ " <tr style=\"text-align: right;\">\n",
129
+ " <th></th>\n",
130
+ " <th>CustomerID</th>\n",
131
+ " <th>Description</th>\n",
132
+ " <th>Quantity</th>\n",
133
+ " <th>UnitPrice</th>\n",
134
+ " <th>InvoiceNo</th>\n",
135
+ " <th>InvoiceDate</th>\n",
136
+ " <th>TotalAmount</th>\n",
137
+ " </tr>\n",
138
+ " </thead>\n",
139
+ " <tbody>\n",
140
+ " <tr>\n",
141
+ " <th>47</th>\n",
142
+ " <td>17850.0</td>\n",
143
+ " <td>HAND WARMER RED POLKA DOT</td>\n",
144
+ " <td>6</td>\n",
145
+ " <td>1.85</td>\n",
146
+ " <td>536372</td>\n",
147
+ " <td>2010-12-01</td>\n",
148
+ " <td>11.10</td>\n",
149
+ " </tr>\n",
150
+ " <tr>\n",
151
+ " <th>48</th>\n",
152
+ " <td>17850.0</td>\n",
153
+ " <td>HAND WARMER UNION JACK</td>\n",
154
+ " <td>6</td>\n",
155
+ " <td>1.85</td>\n",
156
+ " <td>536372</td>\n",
157
+ " <td>2010-12-01</td>\n",
158
+ " <td>11.10</td>\n",
159
+ " </tr>\n",
160
+ " <tr>\n",
161
+ " <th>49</th>\n",
162
+ " <td>17850.0</td>\n",
163
+ " <td>WHITE HANGING HEART T-LIGHT HOLDER</td>\n",
164
+ " <td>6</td>\n",
165
+ " <td>2.55</td>\n",
166
+ " <td>536373</td>\n",
167
+ " <td>2010-12-01</td>\n",
168
+ " <td>15.30</td>\n",
169
+ " </tr>\n",
170
+ " <tr>\n",
171
+ " <th>50</th>\n",
172
+ " <td>17850.0</td>\n",
173
+ " <td>WHITE METAL LANTERN</td>\n",
174
+ " <td>6</td>\n",
175
+ " <td>3.39</td>\n",
176
+ " <td>536373</td>\n",
177
+ " <td>2010-12-01</td>\n",
178
+ " <td>20.34</td>\n",
179
+ " </tr>\n",
180
+ " <tr>\n",
181
+ " <th>51</th>\n",
182
+ " <td>17850.0</td>\n",
183
+ " <td>CREAM CUPID HEARTS COAT HANGER</td>\n",
184
+ " <td>8</td>\n",
185
+ " <td>2.75</td>\n",
186
+ " <td>536373</td>\n",
187
+ " <td>2010-12-01</td>\n",
188
+ " <td>22.00</td>\n",
189
+ " </tr>\n",
190
+ " </tbody>\n",
191
+ "</table>\n",
192
+ "</div>"
193
+ ],
194
+ "text/plain": [
195
+ " CustomerID Description Quantity UnitPrice \n",
196
+ "47 17850.0 HAND WARMER RED POLKA DOT 6 1.85 \\\n",
197
+ "48 17850.0 HAND WARMER UNION JACK 6 1.85 \n",
198
+ "49 17850.0 WHITE HANGING HEART T-LIGHT HOLDER 6 2.55 \n",
199
+ "50 17850.0 WHITE METAL LANTERN 6 3.39 \n",
200
+ "51 17850.0 CREAM CUPID HEARTS COAT HANGER 8 2.75 \n",
201
+ "\n",
202
+ " InvoiceNo InvoiceDate TotalAmount \n",
203
+ "47 536372 2010-12-01 11.10 \n",
204
+ "48 536372 2010-12-01 11.10 \n",
205
+ "49 536373 2010-12-01 15.30 \n",
206
+ "50 536373 2010-12-01 20.34 \n",
207
+ "51 536373 2010-12-01 22.00 "
208
+ ]
209
+ },
210
+ "execution_count": 6,
211
+ "metadata": {},
212
+ "output_type": "execute_result"
213
+ }
214
+ ],
215
+ "source": [
216
+ "df['TotalAmount'] = df['Quantity'] * df['UnitPrice']\n",
217
+ "data_null = df[df['TotalAmount'] < 0]\n",
218
+ "data_null['Quantity'] = - data_null['Quantity']\n",
219
+ "data_null['TotalAmount'] = - data_null['TotalAmount']\n",
220
+ "data_null = data_null[['CustomerID', 'Description', 'Quantity', 'UnitPrice', 'TotalAmount']]\n",
221
+ "\n",
222
+ "data_not_null = df[df['TotalAmount'] >= 0]\n",
223
+ "data_not_null['Quantity'] = - data_not_null['Quantity']\n",
224
+ "data_not_null['TotalAmount'] = - data_not_null['TotalAmount']\n",
225
+ "data_not_null = data_not_null[['CustomerID', 'Description', 'Quantity', 'UnitPrice', 'TotalAmount']]\n",
226
+ "\n",
227
+ "df_concat = pd.concat([data_null, data_not_null])\n",
228
+ "df_concat = df_concat.drop_duplicates()\n",
229
+ "df_concat = df_concat.drop('TotalAmount', axis=1)\n",
230
+ "\n",
231
+ "data_concat = pd.concat([df_concat, df])\n",
232
+ "null_quantity = data_concat[data_concat['Quantity'] < 0]\n",
233
+ "data_concat = data_concat.drop(null_quantity.index)\n",
234
+ "data_concat = data_concat.drop_duplicates(subset=['CustomerID', 'Description', 'Quantity', 'UnitPrice', 'TotalAmount'])\n",
235
+ "data_concat.head()"
236
+ ]
237
+ },
238
+ {
239
+ "attachments": {},
240
+ "cell_type": "markdown",
241
+ "metadata": {},
242
+ "source": [
243
+ "# Functions for RFM"
244
+ ]
245
+ },
246
+ {
247
+ "attachments": {},
248
+ "cell_type": "markdown",
249
+ "metadata": {},
250
+ "source": [
251
+ "Now, we will define several functions : a function to subset our dataframe based on dates and then functions to create our RFM features."
252
+ ]
253
+ },
254
+ {
255
+ "attachments": {},
256
+ "cell_type": "markdown",
257
+ "metadata": {},
258
+ "source": [
259
+ "## Subset dates"
260
+ ]
261
+ },
262
+ {
263
+ "cell_type": "code",
264
+ "execution_count": 7,
265
+ "metadata": {},
266
+ "outputs": [],
267
+ "source": [
268
+ "def data_subset(data, beginning='2010-12-01', end='2011-12-09'):\n",
269
+ " \n",
270
+ " beginning = pd.to_datetime(beginning)\n",
271
+ " end = pd.to_datetime(end)\n",
272
+ " \n",
273
+ " # Subsetting\n",
274
+ " data = data[(data['InvoiceDate'] >= beginning) & (data['InvoiceDate'] <= end)]\n",
275
+ "\n",
276
+ " return data"
277
+ ]
278
+ },
279
+ {
280
+ "attachments": {},
281
+ "cell_type": "markdown",
282
+ "metadata": {},
283
+ "source": [
284
+ "## Recency"
285
+ ]
286
+ },
287
+ {
288
+ "cell_type": "code",
289
+ "execution_count": 8,
290
+ "metadata": {},
291
+ "outputs": [],
292
+ "source": [
293
+ "def recency(data):\n",
294
+ " new_data = data.copy()\n",
295
+ " last_day = new_data['InvoiceDate'].max()\n",
296
+ "\n",
297
+ " recency = []\n",
298
+ " for value in new_data['InvoiceDate']:\n",
299
+ " result = last_day - value\n",
300
+ " recency.append(result.days)\n",
301
+ " \n",
302
+ " new_data['Recency'] = recency\n",
303
+ "\n",
304
+ " return new_data"
305
+ ]
306
+ },
307
+ {
308
+ "attachments": {},
309
+ "cell_type": "markdown",
310
+ "metadata": {},
311
+ "source": [
312
+ "## Frequency"
313
+ ]
314
+ },
315
+ {
316
+ "cell_type": "code",
317
+ "execution_count": 9,
318
+ "metadata": {},
319
+ "outputs": [],
320
+ "source": [
321
+ "def frequency(data):\n",
322
+ " new_data = data.copy()\n",
323
+ "\n",
324
+ " frequencies_series = new_data.groupby('CustomerID')['InvoiceNo'].unique()\n",
325
+ "\n",
326
+ " nb_orders = []\n",
327
+ " for value in frequencies_series.values:\n",
328
+ " nb_orders.append(len(value))\n",
329
+ "\n",
330
+ " indexes = frequencies_series.index\n",
331
+ "\n",
332
+ " df_freq = pd.DataFrame(nb_orders, columns=['NbOrder'])\n",
333
+ " df_freq['CustomerID'] = indexes\n",
334
+ "\n",
335
+ " df_merge = new_data.merge(df_freq, on='CustomerID')\n",
336
+ "\n",
337
+ " df_merge['Frequency'] = df_merge['NbOrder']\n",
338
+ "\n",
339
+ " return df_merge"
340
+ ]
341
+ },
342
+ {
343
+ "attachments": {},
344
+ "cell_type": "markdown",
345
+ "metadata": {},
346
+ "source": [
347
+ "## Monetary"
348
+ ]
349
+ },
350
+ {
351
+ "cell_type": "code",
352
+ "execution_count": 10,
353
+ "metadata": {},
354
+ "outputs": [],
355
+ "source": [
356
+ "def monetary(data):\n",
357
+ " new_data = data.copy()\n",
358
+ " new_data['Monetary'] = new_data['Quantity'] * new_data['UnitPrice']\n",
359
+ "\n",
360
+ " return new_data"
361
+ ]
362
+ },
363
+ {
364
+ "attachments": {},
365
+ "cell_type": "markdown",
366
+ "metadata": {},
367
+ "source": [
368
+ "## Creating our RFM features"
369
+ ]
370
+ },
371
+ {
372
+ "cell_type": "code",
373
+ "execution_count": 11,
374
+ "metadata": {},
375
+ "outputs": [],
376
+ "source": [
377
+ "def rfm(data):\n",
378
+ " new_data = data.copy()\n",
379
+ "\n",
380
+ " # We apply our previously define functions\n",
381
+ " new_data = recency(new_data)\n",
382
+ " new_data = frequency(new_data)\n",
383
+ " new_data = monetary(new_data)\n",
384
+ "\n",
385
+ " # We only keep the features we are interested in\n",
386
+ " df_rfm = new_data[['CustomerID', 'Recency', 'Frequency', 'Monetary']]\n",
387
+ "\n",
388
+ " # We GroupBy to get the total order value per customer\n",
389
+ " monetary_sum = df_rfm.groupby('CustomerID')['Monetary'].sum()\n",
390
+ " monetary_sum = pd.DataFrame(monetary_sum)\n",
391
+ " monetary_sum = monetary_sum.reset_index()\n",
392
+ "\n",
393
+ " # We remove rows with very small total order value\n",
394
+ " # Main issue is that some people returned their order and\n",
395
+ " # either have a total of 0 or they gained money on it\n",
396
+ " # To be safe, we remove those very few rows\n",
397
+ " monetary_sum = monetary_sum[monetary_sum['Monetary'] > 5]\n",
398
+ "\n",
399
+ " df_rfm = df_rfm.drop('Monetary', axis=1)\n",
400
+ " df_rfm = df_rfm.merge(monetary_sum, on='CustomerID')\n",
401
+ " df_rfm = df_rfm.sort_values(['CustomerID', 'Recency'])\n",
402
+ " df_rfm = df_rfm.drop_duplicates(subset='CustomerID')\n",
403
+ " df_rfm = df_rfm.reset_index()\n",
404
+ " df_rfm = df_rfm.drop(['index', 'CustomerID'], axis=1)\n",
405
+ "\n",
406
+ " return df_rfm"
407
+ ]
408
+ },
409
+ {
410
+ "attachments": {},
411
+ "cell_type": "markdown",
412
+ "metadata": {},
413
+ "source": [
414
+ "# Score ARI: at which frequency do we need to update our clusters"
415
+ ]
416
+ },
417
+ {
418
+ "cell_type": "code",
419
+ "execution_count": 12,
420
+ "metadata": {},
421
+ "outputs": [
422
+ {
423
+ "name": "stdout",
424
+ "output_type": "stream",
425
+ "text": [
426
+ "Last Day: 2011-12-09 00:00:00\n",
427
+ "First Day: 2010-12-01 00:00:00\n"
428
+ ]
429
+ }
430
+ ],
431
+ "source": [
432
+ "# We check what if the first and last dates available in our original dataframe\n",
433
+ "last_day = df[f'InvoiceDate'].max()\n",
434
+ "first_day = df['InvoiceDate'].min()\n",
435
+ "print('Last Day:', last_day)\n",
436
+ "print('First Day:', first_day)"
437
+ ]
438
+ },
439
+ {
440
+ "attachments": {},
441
+ "cell_type": "markdown",
442
+ "metadata": {},
443
+ "source": [
444
+ "We split the dataset into two parts: the first 9 months and then the last 3 months."
445
+ ]
446
+ },
447
+ {
448
+ "cell_type": "code",
449
+ "execution_count": 13,
450
+ "metadata": {},
451
+ "outputs": [
452
+ {
453
+ "name": "stdout",
454
+ "output_type": "stream",
455
+ "text": [
456
+ "(1602, 3)\n"
457
+ ]
458
+ },
459
+ {
460
+ "data": {
461
+ "text/html": [
462
+ "<div>\n",
463
+ "<style scoped>\n",
464
+ " .dataframe tbody tr th:only-of-type {\n",
465
+ " vertical-align: middle;\n",
466
+ " }\n",
467
+ "\n",
468
+ " .dataframe tbody tr th {\n",
469
+ " vertical-align: top;\n",
470
+ " }\n",
471
+ "\n",
472
+ " .dataframe thead th {\n",
473
+ " text-align: right;\n",
474
+ " }\n",
475
+ "</style>\n",
476
+ "<table border=\"1\" class=\"dataframe\">\n",
477
+ " <thead>\n",
478
+ " <tr style=\"text-align: right;\">\n",
479
+ " <th></th>\n",
480
+ " <th>Recency</th>\n",
481
+ " <th>Frequency</th>\n",
482
+ " <th>Monetary</th>\n",
483
+ " </tr>\n",
484
+ " </thead>\n",
485
+ " <tbody>\n",
486
+ " <tr>\n",
487
+ " <th>0</th>\n",
488
+ " <td>30</td>\n",
489
+ " <td>3</td>\n",
490
+ " <td>463.41</td>\n",
491
+ " </tr>\n",
492
+ " <tr>\n",
493
+ " <th>1</th>\n",
494
+ " <td>149</td>\n",
495
+ " <td>1</td>\n",
496
+ " <td>40.00</td>\n",
497
+ " </tr>\n",
498
+ " <tr>\n",
499
+ " <th>2</th>\n",
500
+ " <td>163</td>\n",
501
+ " <td>2</td>\n",
502
+ " <td>160.33</td>\n",
503
+ " </tr>\n",
504
+ " <tr>\n",
505
+ " <th>3</th>\n",
506
+ " <td>146</td>\n",
507
+ " <td>1</td>\n",
508
+ " <td>43.80</td>\n",
509
+ " </tr>\n",
510
+ " <tr>\n",
511
+ " <th>4</th>\n",
512
+ " <td>90</td>\n",
513
+ " <td>3</td>\n",
514
+ " <td>310.30</td>\n",
515
+ " </tr>\n",
516
+ " </tbody>\n",
517
+ "</table>\n",
518
+ "</div>"
519
+ ],
520
+ "text/plain": [
521
+ " Recency Frequency Monetary\n",
522
+ "0 30 3 463.41\n",
523
+ "1 149 1 40.00\n",
524
+ "2 163 2 160.33\n",
525
+ "3 146 1 43.80\n",
526
+ "4 90 3 310.30"
527
+ ]
528
+ },
529
+ "execution_count": 13,
530
+ "metadata": {},
531
+ "output_type": "execute_result"
532
+ }
533
+ ],
534
+ "source": [
535
+ "df_rfm = data_subset(data_concat, beginning='2010-12-01', end='2011-09-01')\n",
536
+ "df_rfm_ini = rfm(df_rfm)\n",
537
+ "print(df_rfm_ini.shape)\n",
538
+ "df_rfm_ini.head()"
539
+ ]
540
+ },
541
+ {
542
+ "cell_type": "code",
543
+ "execution_count": 14,
544
+ "metadata": {},
545
+ "outputs": [
546
+ {
547
+ "data": {
548
+ "text/html": [
549
+ "<div>\n",
550
+ "<style scoped>\n",
551
+ " .dataframe tbody tr th:only-of-type {\n",
552
+ " vertical-align: middle;\n",
553
+ " }\n",
554
+ "\n",
555
+ " .dataframe tbody tr th {\n",
556
+ " vertical-align: top;\n",
557
+ " }\n",
558
+ "\n",
559
+ " .dataframe thead th {\n",
560
+ " text-align: right;\n",
561
+ " }\n",
562
+ "</style>\n",
563
+ "<table border=\"1\" class=\"dataframe\">\n",
564
+ " <thead>\n",
565
+ " <tr style=\"text-align: right;\">\n",
566
+ " <th></th>\n",
567
+ " <th>Recency</th>\n",
568
+ " <th>Frequency</th>\n",
569
+ " <th>Monetary</th>\n",
570
+ " </tr>\n",
571
+ " </thead>\n",
572
+ " <tbody>\n",
573
+ " <tr>\n",
574
+ " <th>count</th>\n",
575
+ " <td>1602.000000</td>\n",
576
+ " <td>1602.000000</td>\n",
577
+ " <td>1602.000000</td>\n",
578
+ " </tr>\n",
579
+ " <tr>\n",
580
+ " <th>mean</th>\n",
581
+ " <td>63.584894</td>\n",
582
+ " <td>3.579900</td>\n",
583
+ " <td>383.292091</td>\n",
584
+ " </tr>\n",
585
+ " <tr>\n",
586
+ " <th>std</th>\n",
587
+ " <td>62.502802</td>\n",
588
+ " <td>5.552896</td>\n",
589
+ " <td>1438.844267</td>\n",
590
+ " </tr>\n",
591
+ " <tr>\n",
592
+ " <th>min</th>\n",
593
+ " <td>0.000000</td>\n",
594
+ " <td>1.000000</td>\n",
595
+ " <td>5.040000</td>\n",
596
+ " </tr>\n",
597
+ " <tr>\n",
598
+ " <th>25%</th>\n",
599
+ " <td>16.000000</td>\n",
600
+ " <td>1.000000</td>\n",
601
+ " <td>35.400000</td>\n",
602
+ " </tr>\n",
603
+ " <tr>\n",
604
+ " <th>50%</th>\n",
605
+ " <td>42.000000</td>\n",
606
+ " <td>2.000000</td>\n",
607
+ " <td>100.680000</td>\n",
608
+ " </tr>\n",
609
+ " <tr>\n",
610
+ " <th>75%</th>\n",
611
+ " <td>91.000000</td>\n",
612
+ " <td>4.000000</td>\n",
613
+ " <td>291.115000</td>\n",
614
+ " </tr>\n",
615
+ " <tr>\n",
616
+ " <th>max</th>\n",
617
+ " <td>274.000000</td>\n",
618
+ " <td>89.000000</td>\n",
619
+ " <td>36688.960000</td>\n",
620
+ " </tr>\n",
621
+ " </tbody>\n",
622
+ "</table>\n",
623
+ "</div>"
624
+ ],
625
+ "text/plain": [
626
+ " Recency Frequency Monetary\n",
627
+ "count 1602.000000 1602.000000 1602.000000\n",
628
+ "mean 63.584894 3.579900 383.292091\n",
629
+ "std 62.502802 5.552896 1438.844267\n",
630
+ "min 0.000000 1.000000 5.040000\n",
631
+ "25% 16.000000 1.000000 35.400000\n",
632
+ "50% 42.000000 2.000000 100.680000\n",
633
+ "75% 91.000000 4.000000 291.115000\n",
634
+ "max 274.000000 89.000000 36688.960000"
635
+ ]
636
+ },
637
+ "execution_count": 14,
638
+ "metadata": {},
639
+ "output_type": "execute_result"
640
+ }
641
+ ],
642
+ "source": [
643
+ "# We can see that some people like to order a lot\n",
644
+ "df_rfm_ini.describe()"
645
+ ]
646
+ },
647
+ {
648
+ "attachments": {},
649
+ "cell_type": "markdown",
650
+ "metadata": {},
651
+ "source": [
652
+ "We do a small preprocessing of our features :\n",
653
+ "\n",
654
+ "- We apply a log function to the \"Frequency\" and \"Monetary\" features which are very skewed,\n",
655
+ "- We scale all our features.\n",
656
+ "\n",
657
+ "We need to to it once for our original dataframe (9 months of data) and we'll use this preprocessing later on."
658
+ ]
659
+ },
660
+ {
661
+ "cell_type": "code",
662
+ "execution_count": 15,
663
+ "metadata": {},
664
+ "outputs": [],
665
+ "source": [
666
+ "# Applying a log function\n",
667
+ "df_rfm_ini['Frequency'] = np.log(df_rfm_ini['Frequency'])\n",
668
+ "df_rfm_ini['Monetary'] = np.log(df_rfm_ini['Monetary'])"
669
+ ]
670
+ },
671
+ {
672
+ "cell_type": "code",
673
+ "execution_count": 16,
674
+ "metadata": {},
675
+ "outputs": [
676
+ {
677
+ "data": {
678
+ "text/html": [
679
+ "<div>\n",
680
+ "<style scoped>\n",
681
+ " .dataframe tbody tr th:only-of-type {\n",
682
+ " vertical-align: middle;\n",
683
+ " }\n",
684
+ "\n",
685
+ " .dataframe tbody tr th {\n",
686
+ " vertical-align: top;\n",
687
+ " }\n",
688
+ "\n",
689
+ " .dataframe thead th {\n",
690
+ " text-align: right;\n",
691
+ " }\n",
692
+ "</style>\n",
693
+ "<table border=\"1\" class=\"dataframe\">\n",
694
+ " <thead>\n",
695
+ " <tr style=\"text-align: right;\">\n",
696
+ " <th></th>\n",
697
+ " <th>Recency</th>\n",
698
+ " <th>Frequency</th>\n",
699
+ " <th>Monetary</th>\n",
700
+ " </tr>\n",
701
+ " </thead>\n",
702
+ " <tbody>\n",
703
+ " <tr>\n",
704
+ " <th>0</th>\n",
705
+ " <td>-0.537502</td>\n",
706
+ " <td>0.308927</td>\n",
707
+ " <td>0.985611</td>\n",
708
+ " </tr>\n",
709
+ " <tr>\n",
710
+ " <th>1</th>\n",
711
+ " <td>1.367007</td>\n",
712
+ " <td>-1.014213</td>\n",
713
+ " <td>-0.666035</td>\n",
714
+ " </tr>\n",
715
+ " <tr>\n",
716
+ " <th>2</th>\n",
717
+ " <td>1.591067</td>\n",
718
+ " <td>-0.179405</td>\n",
719
+ " <td>0.270014</td>\n",
720
+ " </tr>\n",
721
+ " <tr>\n",
722
+ " <th>3</th>\n",
723
+ " <td>1.318994</td>\n",
724
+ " <td>-1.014213</td>\n",
725
+ " <td>-0.604847</td>\n",
726
+ " </tr>\n",
727
+ " <tr>\n",
728
+ " <th>4</th>\n",
729
+ " <td>0.422755</td>\n",
730
+ " <td>0.308927</td>\n",
731
+ " <td>0.715202</td>\n",
732
+ " </tr>\n",
733
+ " </tbody>\n",
734
+ "</table>\n",
735
+ "</div>"
736
+ ],
737
+ "text/plain": [
738
+ " Recency Frequency Monetary\n",
739
+ "0 -0.537502 0.308927 0.985611\n",
740
+ "1 1.367007 -1.014213 -0.666035\n",
741
+ "2 1.591067 -0.179405 0.270014\n",
742
+ "3 1.318994 -1.014213 -0.604847\n",
743
+ "4 0.422755 0.308927 0.715202"
744
+ ]
745
+ },
746
+ "execution_count": 16,
747
+ "metadata": {},
748
+ "output_type": "execute_result"
749
+ }
750
+ ],
751
+ "source": [
752
+ "# Scaling our data\n",
753
+ "scaler_ini = StandardScaler()\n",
754
+ "scaler_ini.fit(df_rfm_ini[['Recency', 'Frequency', 'Monetary']])\n",
755
+ "\n",
756
+ "df_rfm_ini[['Recency', 'Frequency', 'Monetary']] = scaler_ini.transform(df_rfm_ini[['Recency', 'Frequency', 'Monetary']])\n",
757
+ "df_rfm_ini = pd.DataFrame(df_rfm_ini, columns=['Recency', 'Frequency', 'Monetary'])\n",
758
+ "df_rfm_ini.head()"
759
+ ]
760
+ },
761
+ {
762
+ "attachments": {},
763
+ "cell_type": "markdown",
764
+ "metadata": {},
765
+ "source": [
766
+ "We create a preprocessing function to be able to compare two kinds of preprocessing (one taking into account the new data, the other using our old data)."
767
+ ]
768
+ },
769
+ {
770
+ "cell_type": "code",
771
+ "execution_count": 17,
772
+ "metadata": {},
773
+ "outputs": [],
774
+ "source": [
775
+ "def preprocessing(data):\n",
776
+ " new_data = data.copy()\n",
777
+ " new_data['Frequency'] = np.log(new_data['Frequency'])\n",
778
+ " new_data['Monetary'] = np.log(new_data['Monetary'])\n",
779
+ "\n",
780
+ " scaler = StandardScaler()\n",
781
+ " scaler.fit(new_data[['Recency', 'Frequency', 'Monetary']])\n",
782
+ "\n",
783
+ " new_data[['Recency', 'Frequency', 'Monetary']] = scaler.transform(new_data[['Recency', 'Frequency', 'Monetary']])\n",
784
+ " new_data = pd.DataFrame(new_data, columns=['Recency', 'Frequency', 'Monetary'])\n",
785
+ "\n",
786
+ " return new_data"
787
+ ]
788
+ },
789
+ {
790
+ "attachments": {},
791
+ "cell_type": "markdown",
792
+ "metadata": {},
793
+ "source": [
794
+ "From pour previous analysis, we know we can cluster our clients into 4 groups.\n",
795
+ "\n",
796
+ "We will use a specific random_state to have smoother results. No matter the random_state, results are fundamentally the same."
797
+ ]
798
+ },
799
+ {
800
+ "cell_type": "code",
801
+ "execution_count": 26,
802
+ "metadata": {},
803
+ "outputs": [
804
+ {
805
+ "data": {
806
+ "text/plain": [
807
+ "(array([0, 1, 2, 3], dtype=int32), array([558, 231, 301, 512]))"
808
+ ]
809
+ },
810
+ "execution_count": 26,
811
+ "metadata": {},
812
+ "output_type": "execute_result"
813
+ }
814
+ ],
815
+ "source": [
816
+ "kmeans = KMeans(n_init='auto', n_clusters=4, random_state=58)\n",
817
+ "kmeans.fit(df_rfm_ini)\n",
818
+ "np.unique(kmeans.labels_, return_counts=True)"
819
+ ]
820
+ },
821
+ {
822
+ "attachments": {},
823
+ "cell_type": "markdown",
824
+ "metadata": {},
825
+ "source": [
826
+ "Now, we want to use our initial preprocessing for the final 3 months of our dataset and then compare with a new preprocessing that would be done each time."
827
+ ]
828
+ },
829
+ {
830
+ "cell_type": "code",
831
+ "execution_count": 27,
832
+ "metadata": {},
833
+ "outputs": [
834
+ {
835
+ "name": "stderr",
836
+ "output_type": "stream",
837
+ "text": [
838
+ "100%|██████████| 16/16 [00:01<00:00, 8.63it/s]\n",
839
+ "100%|██████████| 16/16 [00:01<00:00, 8.66it/s]\n"
840
+ ]
841
+ }
842
+ ],
843
+ "source": [
844
+ "# We start by using the old preprocessing\n",
845
+ "list_df_old = []\n",
846
+ "\n",
847
+ "for i in tqdm(range(0, 112, 7)):\n",
848
+ " # We create the subset\n",
849
+ " new_data = data_subset(data_concat, beginning=date(2010, 12, 1), end=date(2011, 9, 1) + timedelta(days=i))\n",
850
+ "\n",
851
+ " # We get the features\n",
852
+ " new_data_rfm = rfm(new_data)\n",
853
+ "\n",
854
+ " # We do the original preprocessing\n",
855
+ " new_data_rfm['Frequency'] = np.log(new_data_rfm['Frequency'])\n",
856
+ " new_data_rfm['Monetary'] = np.log(new_data_rfm['Monetary'])\n",
857
+ "\n",
858
+ " new_data_rfm[['Recency', 'Frequency', 'Monetary']] = scaler_ini.transform(new_data_rfm[['Recency', 'Frequency', 'Monetary']])\n",
859
+ " new_data_rfm = pd.DataFrame(new_data_rfm, columns=['Recency', 'Frequency', 'Monetary'])\n",
860
+ "\n",
861
+ " # We predict the clusters with our kmeans model\n",
862
+ " y = kmeans.predict(new_data_rfm)\n",
863
+ " new_data_rfm['old_cluster'] = y\n",
864
+ " list_df_old.append(new_data_rfm)\n",
865
+ "\n",
866
+ "# We continue by using a new preprocessing each time\n",
867
+ "list_df_new = []\n",
868
+ "\n",
869
+ "for i in tqdm(range(0, 112, 7)):\n",
870
+ " # We create the subset\n",
871
+ " new_data = data_subset(data_concat, beginning=date(2010, 12, 1), end=date(2011, 9, 1) + timedelta(days=i))\n",
872
+ "\n",
873
+ " # We get the features\n",
874
+ " new_data_rfm = rfm(new_data)\n",
875
+ "\n",
876
+ " # We aplly a new preprocessing\n",
877
+ " new_data_rfm = preprocessing(new_data_rfm)\n",
878
+ " \n",
879
+ " # We predict the clusters by fitting to the new data\n",
880
+ " y = kmeans.fit_predict(new_data_rfm)\n",
881
+ " new_data_rfm['new_cluster'] = y\n",
882
+ " list_df_new.append(new_data_rfm)"
883
+ ]
884
+ },
885
+ {
886
+ "cell_type": "code",
887
+ "execution_count": 28,
888
+ "metadata": {},
889
+ "outputs": [
890
+ {
891
+ "name": "stdout",
892
+ "output_type": "stream",
893
+ "text": [
894
+ "Update needed every 4 weeks.\n"
895
+ ]
896
+ }
897
+ ],
898
+ "source": [
899
+ "# We calcule the ARI between the old and new clustering\n",
900
+ "list_ari = []\n",
901
+ "for i in range(len(list_df_new)):\n",
902
+ " list_ari.append(round(adjusted_rand_score(list_df_new[i]['new_cluster'], list_df_old[i]['old_cluster']), 4))\n",
903
+ "\n",
904
+ "# When the ARI drops below 0.8, an update is necessary\n",
905
+ "update = 0\n",
906
+ "for i in range(len(list_ari)):\n",
907
+ " if list_ari[i] < 0.85:\n",
908
+ " update = i - 1\n",
909
+ " break\n",
910
+ "\n",
911
+ "# Update frequency:\n",
912
+ "print(f'Update needed every {update} weeks.')"
913
+ ]
914
+ },
915
+ {
916
+ "cell_type": "code",
917
+ "execution_count": 30,
918
+ "metadata": {},
919
+ "outputs": [
920
+ {
921
+ "data": {
922
+ "image/png": "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",
923
+ "text/plain": [
924
+ "<Figure size 800x600 with 1 Axes>"
925
+ ]
926
+ },
927
+ "metadata": {},
928
+ "output_type": "display_data"
929
+ }
930
+ ],
931
+ "source": [
932
+ "# Graphically\n",
933
+ "plt.style.use('seaborn-v0_8-darkgrid')\n",
934
+ "plt.figure(figsize=(8, 6))\n",
935
+ "plt.plot(list_ari, marker='o')\n",
936
+ "plt.axhline(y=0.85, color='r', linestyle='--')\n",
937
+ "plt.axvline(x=update, color='r', linestyle='--')\n",
938
+ "\n",
939
+ "plt.title(\"ARI Score for the last 3 months\", fontsize=18)\n",
940
+ "plt.xlabel('Weeks', fontsize=14)\n",
941
+ "plt.ylabel('Adjusted Rand Index Score', fontsize=14)\n",
942
+ "plt.show()"
943
+ ]
944
+ },
945
+ {
946
+ "cell_type": "code",
947
+ "execution_count": null,
948
+ "metadata": {},
949
+ "outputs": [],
950
+ "source": []
951
+ }
952
+ ],
953
+ "metadata": {
954
+ "kernelspec": {
955
+ "display_name": "artefact",
956
+ "language": "python",
957
+ "name": "python3"
958
+ },
959
+ "language_info": {
960
+ "codemirror_mode": {
961
+ "name": "ipython",
962
+ "version": 3
963
+ },
964
+ "file_extension": ".py",
965
+ "mimetype": "text/x-python",
966
+ "name": "python",
967
+ "nbconvert_exporter": "python",
968
+ "pygments_lexer": "ipython3",
969
+ "version": "3.10.4"
970
+ },
971
+ "orig_nbformat": 4
972
+ },
973
+ "nbformat": 4,
974
+ "nbformat_minor": 2
975
+ }
4_customer_segmentation_streamlit_preparation.ipynb ADDED
@@ -0,0 +1,934 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import pandas as pd\n",
10
+ "import numpy as np\n",
11
+ "\n",
12
+ "from datetime import date\n",
13
+ "from datetime import timedelta"
14
+ ]
15
+ },
16
+ {
17
+ "cell_type": "code",
18
+ "execution_count": 2,
19
+ "metadata": {},
20
+ "outputs": [
21
+ {
22
+ "data": {
23
+ "text/html": [
24
+ "<div>\n",
25
+ "<style scoped>\n",
26
+ " .dataframe tbody tr th:only-of-type {\n",
27
+ " vertical-align: middle;\n",
28
+ " }\n",
29
+ "\n",
30
+ " .dataframe tbody tr th {\n",
31
+ " vertical-align: top;\n",
32
+ " }\n",
33
+ "\n",
34
+ " .dataframe thead th {\n",
35
+ " text-align: right;\n",
36
+ " }\n",
37
+ "</style>\n",
38
+ "<table border=\"1\" class=\"dataframe\">\n",
39
+ " <thead>\n",
40
+ " <tr style=\"text-align: right;\">\n",
41
+ " <th></th>\n",
42
+ " <th>InvoiceNo</th>\n",
43
+ " <th>StockCode</th>\n",
44
+ " <th>Description</th>\n",
45
+ " <th>Quantity</th>\n",
46
+ " <th>InvoiceDate</th>\n",
47
+ " <th>UnitPrice</th>\n",
48
+ " <th>CustomerID</th>\n",
49
+ " <th>Country</th>\n",
50
+ " </tr>\n",
51
+ " </thead>\n",
52
+ " <tbody>\n",
53
+ " <tr>\n",
54
+ " <th>0</th>\n",
55
+ " <td>536365</td>\n",
56
+ " <td>85123A</td>\n",
57
+ " <td>WHITE HANGING HEART T-LIGHT HOLDER</td>\n",
58
+ " <td>6</td>\n",
59
+ " <td>12/1/2010 8:26</td>\n",
60
+ " <td>2.55</td>\n",
61
+ " <td>17850.0</td>\n",
62
+ " <td>United Kingdom</td>\n",
63
+ " </tr>\n",
64
+ " <tr>\n",
65
+ " <th>1</th>\n",
66
+ " <td>536365</td>\n",
67
+ " <td>71053</td>\n",
68
+ " <td>WHITE METAL LANTERN</td>\n",
69
+ " <td>6</td>\n",
70
+ " <td>12/1/2010 8:26</td>\n",
71
+ " <td>3.39</td>\n",
72
+ " <td>17850.0</td>\n",
73
+ " <td>United Kingdom</td>\n",
74
+ " </tr>\n",
75
+ " <tr>\n",
76
+ " <th>2</th>\n",
77
+ " <td>536365</td>\n",
78
+ " <td>84406B</td>\n",
79
+ " <td>CREAM CUPID HEARTS COAT HANGER</td>\n",
80
+ " <td>8</td>\n",
81
+ " <td>12/1/2010 8:26</td>\n",
82
+ " <td>2.75</td>\n",
83
+ " <td>17850.0</td>\n",
84
+ " <td>United Kingdom</td>\n",
85
+ " </tr>\n",
86
+ " <tr>\n",
87
+ " <th>3</th>\n",
88
+ " <td>536365</td>\n",
89
+ " <td>84029G</td>\n",
90
+ " <td>KNITTED UNION FLAG HOT WATER BOTTLE</td>\n",
91
+ " <td>6</td>\n",
92
+ " <td>12/1/2010 8:26</td>\n",
93
+ " <td>3.39</td>\n",
94
+ " <td>17850.0</td>\n",
95
+ " <td>United Kingdom</td>\n",
96
+ " </tr>\n",
97
+ " <tr>\n",
98
+ " <th>4</th>\n",
99
+ " <td>536365</td>\n",
100
+ " <td>84029E</td>\n",
101
+ " <td>RED WOOLLY HOTTIE WHITE HEART.</td>\n",
102
+ " <td>6</td>\n",
103
+ " <td>12/1/2010 8:26</td>\n",
104
+ " <td>3.39</td>\n",
105
+ " <td>17850.0</td>\n",
106
+ " <td>United Kingdom</td>\n",
107
+ " </tr>\n",
108
+ " </tbody>\n",
109
+ "</table>\n",
110
+ "</div>"
111
+ ],
112
+ "text/plain": [
113
+ " InvoiceNo StockCode Description Quantity \n",
114
+ "0 536365 85123A WHITE HANGING HEART T-LIGHT HOLDER 6 \\\n",
115
+ "1 536365 71053 WHITE METAL LANTERN 6 \n",
116
+ "2 536365 84406B CREAM CUPID HEARTS COAT HANGER 8 \n",
117
+ "3 536365 84029G KNITTED UNION FLAG HOT WATER BOTTLE 6 \n",
118
+ "4 536365 84029E RED WOOLLY HOTTIE WHITE HEART. 6 \n",
119
+ "\n",
120
+ " InvoiceDate UnitPrice CustomerID Country \n",
121
+ "0 12/1/2010 8:26 2.55 17850.0 United Kingdom \n",
122
+ "1 12/1/2010 8:26 3.39 17850.0 United Kingdom \n",
123
+ "2 12/1/2010 8:26 2.75 17850.0 United Kingdom \n",
124
+ "3 12/1/2010 8:26 3.39 17850.0 United Kingdom \n",
125
+ "4 12/1/2010 8:26 3.39 17850.0 United Kingdom "
126
+ ]
127
+ },
128
+ "execution_count": 2,
129
+ "metadata": {},
130
+ "output_type": "execute_result"
131
+ }
132
+ ],
133
+ "source": [
134
+ "# We load our data\n",
135
+ "df = pd.read_csv('static/customer_segmentation.csv', encoding='latin1')\n",
136
+ "df.head()"
137
+ ]
138
+ },
139
+ {
140
+ "cell_type": "code",
141
+ "execution_count": 3,
142
+ "metadata": {},
143
+ "outputs": [],
144
+ "source": [
145
+ "# We change our dates feature into a datetime type\n",
146
+ "df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'])"
147
+ ]
148
+ },
149
+ {
150
+ "cell_type": "code",
151
+ "execution_count": 4,
152
+ "metadata": {},
153
+ "outputs": [
154
+ {
155
+ "name": "stderr",
156
+ "output_type": "stream",
157
+ "text": [
158
+ "/var/folders/sm/_8w8tt415w56rr72t8421wj00000gn/T/ipykernel_4478/4209121508.py:3: SettingWithCopyWarning: \n",
159
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
160
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
161
+ "\n",
162
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
163
+ " data_null['Quantity'] = - data_null['Quantity']\n",
164
+ "/var/folders/sm/_8w8tt415w56rr72t8421wj00000gn/T/ipykernel_4478/4209121508.py:4: SettingWithCopyWarning: \n",
165
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
166
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
167
+ "\n",
168
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
169
+ " data_null['TotalAmount'] = - data_null['TotalAmount']\n",
170
+ "/var/folders/sm/_8w8tt415w56rr72t8421wj00000gn/T/ipykernel_4478/4209121508.py:8: SettingWithCopyWarning: \n",
171
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
172
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
173
+ "\n",
174
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
175
+ " data_not_null['Quantity'] = - data_not_null['Quantity']\n",
176
+ "/var/folders/sm/_8w8tt415w56rr72t8421wj00000gn/T/ipykernel_4478/4209121508.py:9: SettingWithCopyWarning: \n",
177
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
178
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
179
+ "\n",
180
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
181
+ " data_not_null['TotalAmount'] = - data_not_null['TotalAmount']\n"
182
+ ]
183
+ },
184
+ {
185
+ "data": {
186
+ "text/html": [
187
+ "<div>\n",
188
+ "<style scoped>\n",
189
+ " .dataframe tbody tr th:only-of-type {\n",
190
+ " vertical-align: middle;\n",
191
+ " }\n",
192
+ "\n",
193
+ " .dataframe tbody tr th {\n",
194
+ " vertical-align: top;\n",
195
+ " }\n",
196
+ "\n",
197
+ " .dataframe thead th {\n",
198
+ " text-align: right;\n",
199
+ " }\n",
200
+ "</style>\n",
201
+ "<table border=\"1\" class=\"dataframe\">\n",
202
+ " <thead>\n",
203
+ " <tr style=\"text-align: right;\">\n",
204
+ " <th></th>\n",
205
+ " <th>CustomerID</th>\n",
206
+ " <th>Description</th>\n",
207
+ " <th>Quantity</th>\n",
208
+ " <th>UnitPrice</th>\n",
209
+ " <th>InvoiceNo</th>\n",
210
+ " <th>StockCode</th>\n",
211
+ " <th>InvoiceDate</th>\n",
212
+ " <th>Country</th>\n",
213
+ " <th>TotalAmount</th>\n",
214
+ " </tr>\n",
215
+ " </thead>\n",
216
+ " <tbody>\n",
217
+ " <tr>\n",
218
+ " <th>47</th>\n",
219
+ " <td>17850.0</td>\n",
220
+ " <td>HAND WARMER RED POLKA DOT</td>\n",
221
+ " <td>6</td>\n",
222
+ " <td>1.85</td>\n",
223
+ " <td>536372</td>\n",
224
+ " <td>22632</td>\n",
225
+ " <td>2010-12-01 09:01:00</td>\n",
226
+ " <td>United Kingdom</td>\n",
227
+ " <td>11.10</td>\n",
228
+ " </tr>\n",
229
+ " <tr>\n",
230
+ " <th>48</th>\n",
231
+ " <td>17850.0</td>\n",
232
+ " <td>HAND WARMER UNION JACK</td>\n",
233
+ " <td>6</td>\n",
234
+ " <td>1.85</td>\n",
235
+ " <td>536372</td>\n",
236
+ " <td>22633</td>\n",
237
+ " <td>2010-12-01 09:01:00</td>\n",
238
+ " <td>United Kingdom</td>\n",
239
+ " <td>11.10</td>\n",
240
+ " </tr>\n",
241
+ " <tr>\n",
242
+ " <th>49</th>\n",
243
+ " <td>17850.0</td>\n",
244
+ " <td>WHITE HANGING HEART T-LIGHT HOLDER</td>\n",
245
+ " <td>6</td>\n",
246
+ " <td>2.55</td>\n",
247
+ " <td>536373</td>\n",
248
+ " <td>85123A</td>\n",
249
+ " <td>2010-12-01 09:02:00</td>\n",
250
+ " <td>United Kingdom</td>\n",
251
+ " <td>15.30</td>\n",
252
+ " </tr>\n",
253
+ " <tr>\n",
254
+ " <th>50</th>\n",
255
+ " <td>17850.0</td>\n",
256
+ " <td>WHITE METAL LANTERN</td>\n",
257
+ " <td>6</td>\n",
258
+ " <td>3.39</td>\n",
259
+ " <td>536373</td>\n",
260
+ " <td>71053</td>\n",
261
+ " <td>2010-12-01 09:02:00</td>\n",
262
+ " <td>United Kingdom</td>\n",
263
+ " <td>20.34</td>\n",
264
+ " </tr>\n",
265
+ " <tr>\n",
266
+ " <th>51</th>\n",
267
+ " <td>17850.0</td>\n",
268
+ " <td>CREAM CUPID HEARTS COAT HANGER</td>\n",
269
+ " <td>8</td>\n",
270
+ " <td>2.75</td>\n",
271
+ " <td>536373</td>\n",
272
+ " <td>84406B</td>\n",
273
+ " <td>2010-12-01 09:02:00</td>\n",
274
+ " <td>United Kingdom</td>\n",
275
+ " <td>22.00</td>\n",
276
+ " </tr>\n",
277
+ " </tbody>\n",
278
+ "</table>\n",
279
+ "</div>"
280
+ ],
281
+ "text/plain": [
282
+ " CustomerID Description Quantity UnitPrice \n",
283
+ "47 17850.0 HAND WARMER RED POLKA DOT 6 1.85 \\\n",
284
+ "48 17850.0 HAND WARMER UNION JACK 6 1.85 \n",
285
+ "49 17850.0 WHITE HANGING HEART T-LIGHT HOLDER 6 2.55 \n",
286
+ "50 17850.0 WHITE METAL LANTERN 6 3.39 \n",
287
+ "51 17850.0 CREAM CUPID HEARTS COAT HANGER 8 2.75 \n",
288
+ "\n",
289
+ " InvoiceNo StockCode InvoiceDate Country TotalAmount \n",
290
+ "47 536372 22632 2010-12-01 09:01:00 United Kingdom 11.10 \n",
291
+ "48 536372 22633 2010-12-01 09:01:00 United Kingdom 11.10 \n",
292
+ "49 536373 85123A 2010-12-01 09:02:00 United Kingdom 15.30 \n",
293
+ "50 536373 71053 2010-12-01 09:02:00 United Kingdom 20.34 \n",
294
+ "51 536373 84406B 2010-12-01 09:02:00 United Kingdom 22.00 "
295
+ ]
296
+ },
297
+ "execution_count": 4,
298
+ "metadata": {},
299
+ "output_type": "execute_result"
300
+ }
301
+ ],
302
+ "source": [
303
+ "df['TotalAmount'] = df['Quantity'] * df['UnitPrice']\n",
304
+ "data_null = df[df['TotalAmount'] < 0]\n",
305
+ "data_null['Quantity'] = - data_null['Quantity']\n",
306
+ "data_null['TotalAmount'] = - data_null['TotalAmount']\n",
307
+ "data_null = data_null[['CustomerID', 'Description', 'Quantity', 'UnitPrice', 'TotalAmount']]\n",
308
+ "\n",
309
+ "data_not_null = df[df['TotalAmount'] >= 0]\n",
310
+ "data_not_null['Quantity'] = - data_not_null['Quantity']\n",
311
+ "data_not_null['TotalAmount'] = - data_not_null['TotalAmount']\n",
312
+ "data_not_null = data_not_null[['CustomerID', 'Description', 'Quantity', 'UnitPrice', 'TotalAmount']]\n",
313
+ "\n",
314
+ "df_concat = pd.concat([data_null, data_not_null])\n",
315
+ "df_concat = df_concat.drop_duplicates()\n",
316
+ "df_concat = df_concat.drop('TotalAmount', axis=1)\n",
317
+ "\n",
318
+ "data_concat = pd.concat([df_concat, df])\n",
319
+ "null_quantity = data_concat[data_concat['Quantity'] < 0]\n",
320
+ "data_concat = data_concat.drop(null_quantity.index)\n",
321
+ "data_concat = data_concat.drop_duplicates(subset=['CustomerID', 'Description', 'Quantity', 'UnitPrice', 'TotalAmount'])\n",
322
+ "data_concat.head()"
323
+ ]
324
+ },
325
+ {
326
+ "cell_type": "code",
327
+ "execution_count": 5,
328
+ "metadata": {},
329
+ "outputs": [],
330
+ "source": [
331
+ "def data_subset(data, beginning='2010-12-01', end='2011-12-09'):\n",
332
+ " \n",
333
+ " beginning = pd.to_datetime(beginning)\n",
334
+ " end = pd.to_datetime(end)\n",
335
+ " \n",
336
+ " # Subsetting\n",
337
+ " data = data[(data['InvoiceDate'] >= beginning) & (data['InvoiceDate'] <= end)]\n",
338
+ "\n",
339
+ " return data"
340
+ ]
341
+ },
342
+ {
343
+ "cell_type": "code",
344
+ "execution_count": 6,
345
+ "metadata": {},
346
+ "outputs": [],
347
+ "source": [
348
+ "def recency(data):\n",
349
+ " new_data = data.copy()\n",
350
+ " last_day = new_data['InvoiceDate'].max()\n",
351
+ "\n",
352
+ " recency = []\n",
353
+ " for value in new_data['InvoiceDate']:\n",
354
+ " result = last_day - value\n",
355
+ " recency.append(result.days)\n",
356
+ " \n",
357
+ " new_data['Recency'] = recency\n",
358
+ "\n",
359
+ " return new_data"
360
+ ]
361
+ },
362
+ {
363
+ "cell_type": "code",
364
+ "execution_count": 7,
365
+ "metadata": {},
366
+ "outputs": [],
367
+ "source": [
368
+ "def frequency(data):\n",
369
+ " new_data = data.copy()\n",
370
+ "\n",
371
+ " frequencies_series = new_data.groupby('CustomerID')['InvoiceNo'].unique()\n",
372
+ "\n",
373
+ " nb_orders = []\n",
374
+ " for value in frequencies_series.values:\n",
375
+ " nb_orders.append(len(value))\n",
376
+ "\n",
377
+ " indexes = frequencies_series.index\n",
378
+ "\n",
379
+ " df_freq = pd.DataFrame(nb_orders, columns=['NbOrder'])\n",
380
+ " df_freq['CustomerID'] = indexes\n",
381
+ "\n",
382
+ " df_merge = new_data.merge(df_freq, on='CustomerID')\n",
383
+ "\n",
384
+ " df_merge['Frequency'] = df_merge['NbOrder']\n",
385
+ "\n",
386
+ " return df_merge"
387
+ ]
388
+ },
389
+ {
390
+ "cell_type": "code",
391
+ "execution_count": 8,
392
+ "metadata": {},
393
+ "outputs": [],
394
+ "source": [
395
+ "def monetary(data):\n",
396
+ " new_data = data.copy()\n",
397
+ " new_data['Monetary'] = new_data['Quantity'] * new_data['UnitPrice']\n",
398
+ "\n",
399
+ " return new_data"
400
+ ]
401
+ },
402
+ {
403
+ "cell_type": "code",
404
+ "execution_count": 9,
405
+ "metadata": {},
406
+ "outputs": [],
407
+ "source": [
408
+ "def rfm(data):\n",
409
+ " new_data = data.copy()\n",
410
+ "\n",
411
+ " new_data = recency(new_data)\n",
412
+ " new_data = frequency(new_data)\n",
413
+ " new_data = monetary(new_data)\n",
414
+ "\n",
415
+ " df_rfm = new_data[['CustomerID', 'Recency', 'Frequency', 'Monetary']]\n",
416
+ "\n",
417
+ " monetary_sum = df_rfm.groupby('CustomerID')['Monetary'].sum()\n",
418
+ " monetary_sum = pd.DataFrame(monetary_sum)\n",
419
+ " monetary_sum = monetary_sum.reset_index()\n",
420
+ " monetary_sum = monetary_sum[monetary_sum['Monetary'] > 5]\n",
421
+ "\n",
422
+ " df_rfm = df_rfm.drop('Monetary', axis=1)\n",
423
+ " df_rfm = df_rfm.merge(monetary_sum, on='CustomerID')\n",
424
+ " df_rfm = df_rfm.sort_values(['CustomerID', 'Recency'])\n",
425
+ " df_rfm = df_rfm.drop_duplicates(subset='CustomerID')\n",
426
+ " df_rfm = df_rfm.reset_index()\n",
427
+ " df_rfm = df_rfm.drop(['index'], axis=1)\n",
428
+ "\n",
429
+ " return df_rfm"
430
+ ]
431
+ },
432
+ {
433
+ "cell_type": "code",
434
+ "execution_count": 10,
435
+ "metadata": {},
436
+ "outputs": [
437
+ {
438
+ "data": {
439
+ "text/html": [
440
+ "<div>\n",
441
+ "<style scoped>\n",
442
+ " .dataframe tbody tr th:only-of-type {\n",
443
+ " vertical-align: middle;\n",
444
+ " }\n",
445
+ "\n",
446
+ " .dataframe tbody tr th {\n",
447
+ " vertical-align: top;\n",
448
+ " }\n",
449
+ "\n",
450
+ " .dataframe thead th {\n",
451
+ " text-align: right;\n",
452
+ " }\n",
453
+ "</style>\n",
454
+ "<table border=\"1\" class=\"dataframe\">\n",
455
+ " <thead>\n",
456
+ " <tr style=\"text-align: right;\">\n",
457
+ " <th></th>\n",
458
+ " <th>CustomerID</th>\n",
459
+ " <th>Description</th>\n",
460
+ " <th>Quantity</th>\n",
461
+ " <th>UnitPrice</th>\n",
462
+ " <th>InvoiceNo</th>\n",
463
+ " <th>StockCode</th>\n",
464
+ " <th>InvoiceDate</th>\n",
465
+ " <th>Country</th>\n",
466
+ " <th>TotalAmount</th>\n",
467
+ " <th>Recency</th>\n",
468
+ " <th>NbOrder</th>\n",
469
+ " <th>Frequency</th>\n",
470
+ " <th>Monetary</th>\n",
471
+ " </tr>\n",
472
+ " </thead>\n",
473
+ " <tbody>\n",
474
+ " <tr>\n",
475
+ " <th>0</th>\n",
476
+ " <td>17850.0</td>\n",
477
+ " <td>HAND WARMER RED POLKA DOT</td>\n",
478
+ " <td>6</td>\n",
479
+ " <td>1.85</td>\n",
480
+ " <td>536372</td>\n",
481
+ " <td>22632</td>\n",
482
+ " <td>2010-12-01 09:01:00</td>\n",
483
+ " <td>United Kingdom</td>\n",
484
+ " <td>11.10</td>\n",
485
+ " <td>373</td>\n",
486
+ " <td>9</td>\n",
487
+ " <td>9</td>\n",
488
+ " <td>11.10</td>\n",
489
+ " </tr>\n",
490
+ " <tr>\n",
491
+ " <th>1</th>\n",
492
+ " <td>17850.0</td>\n",
493
+ " <td>HAND WARMER UNION JACK</td>\n",
494
+ " <td>6</td>\n",
495
+ " <td>1.85</td>\n",
496
+ " <td>536372</td>\n",
497
+ " <td>22633</td>\n",
498
+ " <td>2010-12-01 09:01:00</td>\n",
499
+ " <td>United Kingdom</td>\n",
500
+ " <td>11.10</td>\n",
501
+ " <td>373</td>\n",
502
+ " <td>9</td>\n",
503
+ " <td>9</td>\n",
504
+ " <td>11.10</td>\n",
505
+ " </tr>\n",
506
+ " <tr>\n",
507
+ " <th>2</th>\n",
508
+ " <td>17850.0</td>\n",
509
+ " <td>WHITE HANGING HEART T-LIGHT HOLDER</td>\n",
510
+ " <td>6</td>\n",
511
+ " <td>2.55</td>\n",
512
+ " <td>536373</td>\n",
513
+ " <td>85123A</td>\n",
514
+ " <td>2010-12-01 09:02:00</td>\n",
515
+ " <td>United Kingdom</td>\n",
516
+ " <td>15.30</td>\n",
517
+ " <td>373</td>\n",
518
+ " <td>9</td>\n",
519
+ " <td>9</td>\n",
520
+ " <td>15.30</td>\n",
521
+ " </tr>\n",
522
+ " <tr>\n",
523
+ " <th>3</th>\n",
524
+ " <td>17850.0</td>\n",
525
+ " <td>WHITE METAL LANTERN</td>\n",
526
+ " <td>6</td>\n",
527
+ " <td>3.39</td>\n",
528
+ " <td>536373</td>\n",
529
+ " <td>71053</td>\n",
530
+ " <td>2010-12-01 09:02:00</td>\n",
531
+ " <td>United Kingdom</td>\n",
532
+ " <td>20.34</td>\n",
533
+ " <td>373</td>\n",
534
+ " <td>9</td>\n",
535
+ " <td>9</td>\n",
536
+ " <td>20.34</td>\n",
537
+ " </tr>\n",
538
+ " <tr>\n",
539
+ " <th>4</th>\n",
540
+ " <td>17850.0</td>\n",
541
+ " <td>CREAM CUPID HEARTS COAT HANGER</td>\n",
542
+ " <td>8</td>\n",
543
+ " <td>2.75</td>\n",
544
+ " <td>536373</td>\n",
545
+ " <td>84406B</td>\n",
546
+ " <td>2010-12-01 09:02:00</td>\n",
547
+ " <td>United Kingdom</td>\n",
548
+ " <td>22.00</td>\n",
549
+ " <td>373</td>\n",
550
+ " <td>9</td>\n",
551
+ " <td>9</td>\n",
552
+ " <td>22.00</td>\n",
553
+ " </tr>\n",
554
+ " </tbody>\n",
555
+ "</table>\n",
556
+ "</div>"
557
+ ],
558
+ "text/plain": [
559
+ " CustomerID Description Quantity UnitPrice \n",
560
+ "0 17850.0 HAND WARMER RED POLKA DOT 6 1.85 \\\n",
561
+ "1 17850.0 HAND WARMER UNION JACK 6 1.85 \n",
562
+ "2 17850.0 WHITE HANGING HEART T-LIGHT HOLDER 6 2.55 \n",
563
+ "3 17850.0 WHITE METAL LANTERN 6 3.39 \n",
564
+ "4 17850.0 CREAM CUPID HEARTS COAT HANGER 8 2.75 \n",
565
+ "\n",
566
+ " InvoiceNo StockCode InvoiceDate Country TotalAmount \n",
567
+ "0 536372 22632 2010-12-01 09:01:00 United Kingdom 11.10 \\\n",
568
+ "1 536372 22633 2010-12-01 09:01:00 United Kingdom 11.10 \n",
569
+ "2 536373 85123A 2010-12-01 09:02:00 United Kingdom 15.30 \n",
570
+ "3 536373 71053 2010-12-01 09:02:00 United Kingdom 20.34 \n",
571
+ "4 536373 84406B 2010-12-01 09:02:00 United Kingdom 22.00 \n",
572
+ "\n",
573
+ " Recency NbOrder Frequency Monetary \n",
574
+ "0 373 9 9 11.10 \n",
575
+ "1 373 9 9 11.10 \n",
576
+ "2 373 9 9 15.30 \n",
577
+ "3 373 9 9 20.34 \n",
578
+ "4 373 9 9 22.00 "
579
+ ]
580
+ },
581
+ "execution_count": 10,
582
+ "metadata": {},
583
+ "output_type": "execute_result"
584
+ }
585
+ ],
586
+ "source": [
587
+ "# We apply our functions to get the new features we are interested in\n",
588
+ "df_info = recency(data_concat)\n",
589
+ "df_info = frequency(df_info)\n",
590
+ "df_info = monetary(df_info)\n",
591
+ "df_info.head()"
592
+ ]
593
+ },
594
+ {
595
+ "cell_type": "code",
596
+ "execution_count": 11,
597
+ "metadata": {},
598
+ "outputs": [
599
+ {
600
+ "data": {
601
+ "text/html": [
602
+ "<div>\n",
603
+ "<style scoped>\n",
604
+ " .dataframe tbody tr th:only-of-type {\n",
605
+ " vertical-align: middle;\n",
606
+ " }\n",
607
+ "\n",
608
+ " .dataframe tbody tr th {\n",
609
+ " vertical-align: top;\n",
610
+ " }\n",
611
+ "\n",
612
+ " .dataframe thead th {\n",
613
+ " text-align: right;\n",
614
+ " }\n",
615
+ "</style>\n",
616
+ "<table border=\"1\" class=\"dataframe\">\n",
617
+ " <thead>\n",
618
+ " <tr style=\"text-align: right;\">\n",
619
+ " <th></th>\n",
620
+ " <th>CustomerID</th>\n",
621
+ " <th>Description</th>\n",
622
+ " <th>Quantity</th>\n",
623
+ " <th>InvoiceNo</th>\n",
624
+ " <th>InvoiceDate</th>\n",
625
+ " <th>TotalAmount</th>\n",
626
+ " <th>Recency</th>\n",
627
+ " <th>NbOrder</th>\n",
628
+ " <th>Frequency</th>\n",
629
+ " <th>Monetary</th>\n",
630
+ " </tr>\n",
631
+ " </thead>\n",
632
+ " <tbody>\n",
633
+ " <tr>\n",
634
+ " <th>0</th>\n",
635
+ " <td>17850.0</td>\n",
636
+ " <td>HAND WARMER RED POLKA DOT</td>\n",
637
+ " <td>6</td>\n",
638
+ " <td>536372</td>\n",
639
+ " <td>2010-12-01 09:01:00</td>\n",
640
+ " <td>11.10</td>\n",
641
+ " <td>373</td>\n",
642
+ " <td>9</td>\n",
643
+ " <td>9</td>\n",
644
+ " <td>11.10</td>\n",
645
+ " </tr>\n",
646
+ " <tr>\n",
647
+ " <th>1</th>\n",
648
+ " <td>17850.0</td>\n",
649
+ " <td>HAND WARMER UNION JACK</td>\n",
650
+ " <td>6</td>\n",
651
+ " <td>536372</td>\n",
652
+ " <td>2010-12-01 09:01:00</td>\n",
653
+ " <td>11.10</td>\n",
654
+ " <td>373</td>\n",
655
+ " <td>9</td>\n",
656
+ " <td>9</td>\n",
657
+ " <td>11.10</td>\n",
658
+ " </tr>\n",
659
+ " <tr>\n",
660
+ " <th>2</th>\n",
661
+ " <td>17850.0</td>\n",
662
+ " <td>WHITE HANGING HEART T-LIGHT HOLDER</td>\n",
663
+ " <td>6</td>\n",
664
+ " <td>536373</td>\n",
665
+ " <td>2010-12-01 09:02:00</td>\n",
666
+ " <td>15.30</td>\n",
667
+ " <td>373</td>\n",
668
+ " <td>9</td>\n",
669
+ " <td>9</td>\n",
670
+ " <td>15.30</td>\n",
671
+ " </tr>\n",
672
+ " <tr>\n",
673
+ " <th>3</th>\n",
674
+ " <td>17850.0</td>\n",
675
+ " <td>WHITE METAL LANTERN</td>\n",
676
+ " <td>6</td>\n",
677
+ " <td>536373</td>\n",
678
+ " <td>2010-12-01 09:02:00</td>\n",
679
+ " <td>20.34</td>\n",
680
+ " <td>373</td>\n",
681
+ " <td>9</td>\n",
682
+ " <td>9</td>\n",
683
+ " <td>20.34</td>\n",
684
+ " </tr>\n",
685
+ " <tr>\n",
686
+ " <th>4</th>\n",
687
+ " <td>17850.0</td>\n",
688
+ " <td>CREAM CUPID HEARTS COAT HANGER</td>\n",
689
+ " <td>8</td>\n",
690
+ " <td>536373</td>\n",
691
+ " <td>2010-12-01 09:02:00</td>\n",
692
+ " <td>22.00</td>\n",
693
+ " <td>373</td>\n",
694
+ " <td>9</td>\n",
695
+ " <td>9</td>\n",
696
+ " <td>22.00</td>\n",
697
+ " </tr>\n",
698
+ " </tbody>\n",
699
+ "</table>\n",
700
+ "</div>"
701
+ ],
702
+ "text/plain": [
703
+ " CustomerID Description Quantity InvoiceNo \n",
704
+ "0 17850.0 HAND WARMER RED POLKA DOT 6 536372 \\\n",
705
+ "1 17850.0 HAND WARMER UNION JACK 6 536372 \n",
706
+ "2 17850.0 WHITE HANGING HEART T-LIGHT HOLDER 6 536373 \n",
707
+ "3 17850.0 WHITE METAL LANTERN 6 536373 \n",
708
+ "4 17850.0 CREAM CUPID HEARTS COAT HANGER 8 536373 \n",
709
+ "\n",
710
+ " InvoiceDate TotalAmount Recency NbOrder Frequency Monetary \n",
711
+ "0 2010-12-01 09:01:00 11.10 373 9 9 11.10 \n",
712
+ "1 2010-12-01 09:01:00 11.10 373 9 9 11.10 \n",
713
+ "2 2010-12-01 09:02:00 15.30 373 9 9 15.30 \n",
714
+ "3 2010-12-01 09:02:00 20.34 373 9 9 20.34 \n",
715
+ "4 2010-12-01 09:02:00 22.00 373 9 9 22.00 "
716
+ ]
717
+ },
718
+ "execution_count": 11,
719
+ "metadata": {},
720
+ "output_type": "execute_result"
721
+ }
722
+ ],
723
+ "source": [
724
+ "# We drop features we won't use\n",
725
+ "df_info = df_info.drop(['StockCode', 'Country', 'UnitPrice'], axis=1)\n",
726
+ "df_info.head()"
727
+ ]
728
+ },
729
+ {
730
+ "attachments": {},
731
+ "cell_type": "markdown",
732
+ "metadata": {},
733
+ "source": [
734
+ "We apply our rfm function to have the list of CustomerID values that have a \"Monetary\" feature which is positive."
735
+ ]
736
+ },
737
+ {
738
+ "cell_type": "code",
739
+ "execution_count": 12,
740
+ "metadata": {},
741
+ "outputs": [
742
+ {
743
+ "data": {
744
+ "text/plain": [
745
+ "array([12347., 12348., 12349., ..., 18282., 18283., 18287.])"
746
+ ]
747
+ },
748
+ "execution_count": 12,
749
+ "metadata": {},
750
+ "output_type": "execute_result"
751
+ }
752
+ ],
753
+ "source": [
754
+ "df_customers = rfm(df)\n",
755
+ "customers = df_customers['CustomerID'].unique()\n",
756
+ "customers"
757
+ ]
758
+ },
759
+ {
760
+ "cell_type": "code",
761
+ "execution_count": 13,
762
+ "metadata": {},
763
+ "outputs": [
764
+ {
765
+ "data": {
766
+ "text/html": [
767
+ "<div>\n",
768
+ "<style scoped>\n",
769
+ " .dataframe tbody tr th:only-of-type {\n",
770
+ " vertical-align: middle;\n",
771
+ " }\n",
772
+ "\n",
773
+ " .dataframe tbody tr th {\n",
774
+ " vertical-align: top;\n",
775
+ " }\n",
776
+ "\n",
777
+ " .dataframe thead th {\n",
778
+ " text-align: right;\n",
779
+ " }\n",
780
+ "</style>\n",
781
+ "<table border=\"1\" class=\"dataframe\">\n",
782
+ " <thead>\n",
783
+ " <tr style=\"text-align: right;\">\n",
784
+ " <th></th>\n",
785
+ " <th>CustomerID</th>\n",
786
+ " <th>Description</th>\n",
787
+ " <th>Quantity</th>\n",
788
+ " <th>InvoiceNo</th>\n",
789
+ " <th>InvoiceDate</th>\n",
790
+ " <th>TotalAmount</th>\n",
791
+ " <th>Recency</th>\n",
792
+ " <th>NbOrder</th>\n",
793
+ " <th>Frequency</th>\n",
794
+ " <th>Monetary</th>\n",
795
+ " </tr>\n",
796
+ " </thead>\n",
797
+ " <tbody>\n",
798
+ " <tr>\n",
799
+ " <th>0</th>\n",
800
+ " <td>17850.0</td>\n",
801
+ " <td>HAND WARMER RED POLKA DOT</td>\n",
802
+ " <td>6</td>\n",
803
+ " <td>536372</td>\n",
804
+ " <td>2010-12-01 09:01:00</td>\n",
805
+ " <td>11.10</td>\n",
806
+ " <td>373</td>\n",
807
+ " <td>9</td>\n",
808
+ " <td>9</td>\n",
809
+ " <td>11.10</td>\n",
810
+ " </tr>\n",
811
+ " <tr>\n",
812
+ " <th>1</th>\n",
813
+ " <td>17850.0</td>\n",
814
+ " <td>HAND WARMER UNION JACK</td>\n",
815
+ " <td>6</td>\n",
816
+ " <td>536372</td>\n",
817
+ " <td>2010-12-01 09:01:00</td>\n",
818
+ " <td>11.10</td>\n",
819
+ " <td>373</td>\n",
820
+ " <td>9</td>\n",
821
+ " <td>9</td>\n",
822
+ " <td>11.10</td>\n",
823
+ " </tr>\n",
824
+ " <tr>\n",
825
+ " <th>2</th>\n",
826
+ " <td>17850.0</td>\n",
827
+ " <td>WHITE HANGING HEART T-LIGHT HOLDER</td>\n",
828
+ " <td>6</td>\n",
829
+ " <td>536373</td>\n",
830
+ " <td>2010-12-01 09:02:00</td>\n",
831
+ " <td>15.30</td>\n",
832
+ " <td>373</td>\n",
833
+ " <td>9</td>\n",
834
+ " <td>9</td>\n",
835
+ " <td>15.30</td>\n",
836
+ " </tr>\n",
837
+ " <tr>\n",
838
+ " <th>3</th>\n",
839
+ " <td>17850.0</td>\n",
840
+ " <td>WHITE METAL LANTERN</td>\n",
841
+ " <td>6</td>\n",
842
+ " <td>536373</td>\n",
843
+ " <td>2010-12-01 09:02:00</td>\n",
844
+ " <td>20.34</td>\n",
845
+ " <td>373</td>\n",
846
+ " <td>9</td>\n",
847
+ " <td>9</td>\n",
848
+ " <td>20.34</td>\n",
849
+ " </tr>\n",
850
+ " <tr>\n",
851
+ " <th>4</th>\n",
852
+ " <td>17850.0</td>\n",
853
+ " <td>CREAM CUPID HEARTS COAT HANGER</td>\n",
854
+ " <td>8</td>\n",
855
+ " <td>536373</td>\n",
856
+ " <td>2010-12-01 09:02:00</td>\n",
857
+ " <td>22.00</td>\n",
858
+ " <td>373</td>\n",
859
+ " <td>9</td>\n",
860
+ " <td>9</td>\n",
861
+ " <td>22.00</td>\n",
862
+ " </tr>\n",
863
+ " </tbody>\n",
864
+ "</table>\n",
865
+ "</div>"
866
+ ],
867
+ "text/plain": [
868
+ " CustomerID Description Quantity InvoiceNo \n",
869
+ "0 17850.0 HAND WARMER RED POLKA DOT 6 536372 \\\n",
870
+ "1 17850.0 HAND WARMER UNION JACK 6 536372 \n",
871
+ "2 17850.0 WHITE HANGING HEART T-LIGHT HOLDER 6 536373 \n",
872
+ "3 17850.0 WHITE METAL LANTERN 6 536373 \n",
873
+ "4 17850.0 CREAM CUPID HEARTS COAT HANGER 8 536373 \n",
874
+ "\n",
875
+ " InvoiceDate TotalAmount Recency NbOrder Frequency Monetary \n",
876
+ "0 2010-12-01 09:01:00 11.10 373 9 9 11.10 \n",
877
+ "1 2010-12-01 09:01:00 11.10 373 9 9 11.10 \n",
878
+ "2 2010-12-01 09:02:00 15.30 373 9 9 15.30 \n",
879
+ "3 2010-12-01 09:02:00 20.34 373 9 9 20.34 \n",
880
+ "4 2010-12-01 09:02:00 22.00 373 9 9 22.00 "
881
+ ]
882
+ },
883
+ "execution_count": 13,
884
+ "metadata": {},
885
+ "output_type": "execute_result"
886
+ }
887
+ ],
888
+ "source": [
889
+ "# We apply to get only the interesting IDs\n",
890
+ "df_info = df_info[df_info['CustomerID'].isin(customers)]\n",
891
+ "df_info.head()"
892
+ ]
893
+ },
894
+ {
895
+ "cell_type": "code",
896
+ "execution_count": 14,
897
+ "metadata": {},
898
+ "outputs": [],
899
+ "source": [
900
+ "# We save as a CSV file\n",
901
+ "df_info.to_csv('static/customer_info.csv')"
902
+ ]
903
+ },
904
+ {
905
+ "cell_type": "code",
906
+ "execution_count": null,
907
+ "metadata": {},
908
+ "outputs": [],
909
+ "source": []
910
+ }
911
+ ],
912
+ "metadata": {
913
+ "kernelspec": {
914
+ "display_name": "artefact",
915
+ "language": "python",
916
+ "name": "python3"
917
+ },
918
+ "language_info": {
919
+ "codemirror_mode": {
920
+ "name": "ipython",
921
+ "version": 3
922
+ },
923
+ "file_extension": ".py",
924
+ "mimetype": "text/x-python",
925
+ "name": "python",
926
+ "nbconvert_exporter": "python",
927
+ "pygments_lexer": "ipython3",
928
+ "version": "3.10.4"
929
+ },
930
+ "orig_nbformat": 4
931
+ },
932
+ "nbformat": 4,
933
+ "nbformat_minor": 2
934
+ }
5_customer_segmentation_streamlit.py ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import seaborn as sns
5
+ from PIL import Image
6
+ import datetime
7
+ import streamlit as st
8
+
9
+ #####################################################################################################################################
10
+ st.set_page_config(layout='wide')
11
+
12
+ # Sidebar: Image + main info on dataset
13
+ def data_subset(data, beginning='2010-12-01', end='2011-12-09'):
14
+
15
+ beginning = pd.to_datetime(beginning)
16
+ end = pd.to_datetime(end)
17
+
18
+ # Subsetting
19
+ data = data[(data['InvoiceDate'] >= beginning) & (data['InvoiceDate'] <= end)]
20
+
21
+ return data
22
+
23
+ # Loading datasets
24
+ df_info = pd.read_csv('static/customer_info.csv')
25
+ df_info['InvoiceDate'] = pd.to_datetime(df_info['InvoiceDate'])
26
+
27
+ with st.sidebar:
28
+ col1, col2, col3 = st.columns(3)
29
+ with col2:
30
+ random_image = Image.open('static/logo_random.png')
31
+ st.image(random_image)
32
+
33
+ # Showing top products
34
+ if st.checkbox('Check to see top products sold in a selected timeframe'):
35
+ start = st.date_input('Input beginning of the wanted timeframe', datetime.date(2010, 12, 1),
36
+ min_value=datetime.date(2010, 12, 1), max_value=datetime.date(2011, 12, 9), key=1)
37
+ end = st.date_input('Input beginning of the wanted timeframe', datetime.date(2011, 12, 9),
38
+ min_value=start, max_value=datetime.date(2011, 12, 9), key=2)
39
+
40
+ df_top_products = df_info.copy()
41
+ df_subset_products = data_subset(df_top_products, start, end)
42
+
43
+ df_subset_products = df_top_products.groupby('Description')['Quantity'].sum()
44
+ number_chosen_products = st.number_input('How many top products sold do you want to see?', value=5)
45
+ df_subset_products_top = pd.DataFrame(df_top_products.sort_values(by='Quantity', ascending=False)).iloc[:number_chosen_products,:]
46
+ df_subset_products_top = df_subset_products_top[['Description', 'Quantity']]
47
+ st.dataframe(df_subset_products_top)
48
+
49
+ # Showing most recent clients
50
+ if st.checkbox('Check to see the most recent customers in a selected timeframe'):
51
+ start_clts = st.date_input('Input beginning of the wanted timeframe', datetime.date(2010, 12, 1),
52
+ min_value=datetime.date(2010, 12, 1), max_value=datetime.date(2011, 12, 9), key=3)
53
+ end_clts = st.date_input('Input beginning of the wanted timeframe', datetime.date(2011, 12, 9),
54
+ min_value=start_clts, max_value=datetime.date(2011, 12, 9), key=4)
55
+ df_recent_customers = df_info.copy()
56
+ df_subset_recent_customers = data_subset(df_recent_customers, start_clts, end_clts)
57
+
58
+ df_subset_recent_customers = df_subset_recent_customers.groupby('CustomerID')['Recency'].min()
59
+ number_chosen_recency = st.number_input('How many recent customers do you want to see?', value=5)
60
+ df_subset_recent_customers_top = pd.DataFrame(df_subset_recent_customers.sort_values()).iloc[:number_chosen_recency,:]
61
+ st.dataframe(df_subset_recent_customers_top)
62
+
63
+ # Showing most prolific customers
64
+ if st.checkbox('Check to see the top customers in a selected timeframe'):
65
+ start_top = st.date_input('Input beginning of the wanted timeframe', datetime.date(2010, 12, 1),
66
+ min_value=datetime.date(2010, 12, 1), max_value=datetime.date(2011, 12, 9), key=5)
67
+ end_top = st.date_input('Input beginning of the wanted timeframe', datetime.date(2011, 12, 9),
68
+ min_value=start_top, max_value=datetime.date(2011, 12, 9), key=6)
69
+ df_top_customers = df_info.copy()
70
+ df_subset_top_customers = data_subset(df_top_customers, start_top, end_top)
71
+
72
+ df_subset_top_customers = df_subset_top_customers.groupby('CustomerID')['Monetary'].sum()
73
+ number_chosen_top_clts = st.number_input('How many top customers do you want to see?', value=5)
74
+ df_subset_top_customers_top = pd.DataFrame(df_subset_top_customers.sort_values(ascending=False)).iloc[:number_chosen_top_clts,:]
75
+ st.dataframe(df_subset_top_customers_top)
76
+
77
+ #####################################################################################################################################
78
+ st.title('E-commerce: client dashboard')
79
+ st.write("---")
80
+
81
+ # Loading dataset
82
+ df_info_customer = df_info.copy()
83
+ customer_id_default = int(df_info_customer['CustomerID'].min())
84
+
85
+ # We choose a CustomerID
86
+ st.number_input('CustomerID', min_value=customer_id_default, value=customer_id_default, step=1, format="%d", key='customer_id')
87
+ customer_id = st.session_state.customer_id
88
+
89
+ if customer_id not in df_info_customer['CustomerID'].values:
90
+ st.write('This CustomerID is not available right now, please find another.')
91
+
92
+ else:
93
+ start_info = st.date_input('Input beginning of the wanted timeframe', datetime.date(2010, 12, 1),
94
+ min_value=datetime.date(2010, 12, 1), max_value=datetime.date(2011, 12, 9), key=7)
95
+ end_info = st.date_input('Input beginning of the wanted timeframe', datetime.date(2011, 12, 9),
96
+ min_value=start_info, max_value=datetime.date(2011, 12, 9), key=8)
97
+ df_subset_info_customer = data_subset(df_info_customer, start_info, end_info)
98
+
99
+ # Main info (recency, number of orders, how much the customer spent)
100
+ df_subset_info_customer = df_subset_info_customer[df_subset_info_customer['CustomerID'] == customer_id]
101
+ df_main_info = df_subset_info_customer.groupby('CustomerID').agg(Recency=('Recency', 'min'), NbOrder=('NbOrder', 'max'), MonetaryTotal=('Monetary', 'sum'))
102
+
103
+ # GroupBy to get the mean value of each order for the customer
104
+ df_mean_order = df_subset_info_customer.groupby(['InvoiceNo', 'CustomerID']).agg(TotalOrderValue=('Monetary', 'sum'))
105
+ df_mean_order = df_mean_order.groupby('CustomerID').agg(MeanOrderValue=('TotalOrderValue', 'mean'))
106
+
107
+ # GroupBy to get the most bought product and its quantity
108
+ df_product_clts = pd.DataFrame(df_info.groupby(['CustomerID','Description'])['Quantity'].sum())
109
+ df_product_clts = df_product_clts.reset_index()
110
+ df_product_clts = df_product_clts[df_product_clts['CustomerID'] == customer_id]
111
+ ids, values = df_product_clts.groupby('CustomerID')['Quantity'].max().index, df_product_clts.groupby('CustomerID')['Quantity'].max().values
112
+ df_product_clts = df_product_clts[(df_product_clts['CustomerID'] == ids[0]) & (df_product_clts['Quantity'] == values[0])]
113
+
114
+ # Now we create the columns we want
115
+ df_main_info['MeanOrderValue'] = df_mean_order['MeanOrderValue'].values[0]
116
+ df_main_info['MostOrderedProduct'] = df_product_clts['Description'].values[0]
117
+ df_main_info['MostOrderedProductQuantity'] = df_product_clts['Quantity'].values[0]
118
+
119
+ # We can show it now that it's complete
120
+ st.dataframe(df_main_info)
121
+
122
+ st.write("---")
123
+ #####################################################################################################################################
124
+ st.subheader('Similarity between customers:')
125
+ with st.expander('Choose a number of similar customers to compare:'):
126
+
127
+ if st.checkbox('Only similar customers:'):
128
+ options_similar = ['Recency', 'NbOrder', 'MonetaryTotal', 'MeanOrderValue']
129
+ option_similar = st.selectbox('Choose a feature to plot:', tuple(options_similar))
130
+
131
+ df_similar_customer = df_info.copy()
132
+
133
+ # Main info (recency, number of orders, how much the customer spent)
134
+ df_similar_customer_grouped = df_similar_customer.groupby('CustomerID').agg(Recency=('Recency', 'min'), NbOrder=('NbOrder', 'max'), MonetaryTotal=('Monetary', 'sum'))
135
+
136
+ # GroupBy to get the mean value of each order for the customer
137
+ df_mean_order_similar = df_similar_customer.groupby(['InvoiceNo', 'CustomerID']).agg(TotalOrderValue=('Monetary', 'sum'))
138
+ df_mean_order_similar = df_mean_order_similar.groupby('CustomerID').agg(MeanOrderValue=('TotalOrderValue', 'mean'))
139
+
140
+ # Now we create the column we want
141
+ df_similar_customer_grouped['MeanOrderValue'] = df_mean_order_similar['MeanOrderValue'].values
142
+
143
+ # We select the client
144
+ df_similar_customer_grouped = df_similar_customer_grouped.reset_index()
145
+ df_selected_clt = df_similar_customer_grouped[df_similar_customer_grouped['CustomerID'] == customer_id]
146
+
147
+ # We calculate distances (euclidean)
148
+ distances = []
149
+ for i in range(df_similar_customer_grouped.shape[0]):
150
+ distance = np.linalg.norm(df_similar_customer_grouped.drop('CustomerID', axis=1).values[i] - df_selected_clt.drop('CustomerID', axis=1).values)
151
+ distances.append(distance)
152
+
153
+ n_neighbors = st.slider("Number of similar customers:", min_value=5, max_value=30, value=10)
154
+ neighbors = sorted(distances)[:n_neighbors]
155
+
156
+ # We get the indices of the similar customers
157
+ indices_neighbors = []
158
+ for i in range(len(neighbors)):
159
+ indices_neighbors.append(distances.index(neighbors[i]))
160
+
161
+ df_neighbors_selected = df_similar_customer_grouped.iloc[indices_neighbors, :]
162
+
163
+ fig2, ax = plt.subplots()
164
+ ax.set_xlabel('Customers', fontsize=17)
165
+ ax.set_ylabel(option_similar, fontsize=17)
166
+ ax.axhline(y=df_selected_clt[option_similar].values, color='r', label='axhline - full height')
167
+ ax = plt.boxplot(df_neighbors_selected[option_similar], showfliers=False)
168
+
169
+ st.pyplot(fig2)
170
+
171
+ if st.checkbox('Compare to all customers:'):
172
+ options_all = ['Recency', 'NbOrder', 'MonetaryTotal', 'MeanOrderValue']
173
+ option_all = st.selectbox('Choose a feature to plot:', tuple(options_all))
174
+
175
+ df_all_customer = df_info.copy()
176
+
177
+ # Main info (recency, number of orders, how much the customer spent)
178
+ df_all_customer_grouped = df_all_customer.groupby('CustomerID').agg(Recency=('Recency', 'min'), NbOrder=('NbOrder', 'max'), MonetaryTotal=('Monetary', 'sum'))
179
+
180
+ # GroupBy to get the mean value of each order for the customer
181
+ df_mean_order_all = df_all_customer.groupby(['InvoiceNo', 'CustomerID']).agg(TotalOrderValue=('Monetary', 'sum'))
182
+ df_mean_order_all = df_mean_order_all.groupby('CustomerID').agg(MeanOrderValue=('TotalOrderValue', 'mean'))
183
+
184
+ # Now we create the column we want
185
+ df_all_customer_grouped['MeanOrderValue'] = df_mean_order_all['MeanOrderValue'].values
186
+
187
+ # We select the client
188
+ df_selected_clt_all = df_all_customer_grouped.reset_index()
189
+ df_selected_clt_all = df_selected_clt_all[df_selected_clt_all['CustomerID'] == customer_id]
190
+
191
+ # We calculate distances (euclidean)
192
+ distances = []
193
+ for i in range(df_all_customer_grouped.shape[0]):
194
+ distance = np.linalg.norm(df_all_customer_grouped.values[i] - df_selected_clt_all.drop('CustomerID', axis=1).values)
195
+ distances.append(distance)
196
+
197
+ fig2, ax = plt.subplots()
198
+ ax.set_xlabel('Customers', fontsize=17)
199
+ ax.set_ylabel(option_all, fontsize=17)
200
+ ax.axhline(y=df_selected_clt_all[option_all].values, color='r', label='axhline - full height')
201
+ ax = plt.boxplot(df_all_customer_grouped[option_all], showfliers=False)
202
+
203
+ st.pyplot(fig2)
204
+
205
+ st.write("---")
206
+ #####################################################################################################################################
207
+ st.subheader('Barplot of top selected products in the selected timeframe:')
208
+ with st.expander('Select to choose how many top products you want to see and in which timeframe'):
209
+
210
+ start_product_date = st.date_input('Input beginning of the wanted timeframe', datetime.date(2010, 12, 1),
211
+ min_value=datetime.date(2010, 12, 1), max_value=datetime.date(2011, 12, 9), key=9)
212
+ end_product_date = st.date_input('Input beginning of the wanted timeframe', datetime.date(2011, 12, 9),
213
+ min_value=start_product_date, max_value=datetime.date(2011, 12, 9), key=10)
214
+ df_top_products_plot = df_info.copy()
215
+ df_subset_products = data_subset(df_top_products_plot, start_product_date, end_product_date)
216
+ start_product, end_product = st.select_slider('Select a range of top product', options=[x for x in range(1, 21)], value=(1, 10))
217
+ df_subset_products = df_subset_products.groupby('Description')['Quantity'].sum()
218
+ df_subset_products = df_subset_products.reset_index()
219
+ df_slider_products = df_subset_products.sort_values(by='Quantity', ascending=False)
220
+ df_slider_products = df_slider_products.iloc[start_product-1:end_product, :]
221
+
222
+ fig, ax = plt.subplots()
223
+ bars = plt.barh(y=df_slider_products['Description'], width=df_slider_products['Quantity'], color=['darkmagenta', 'darkblue', 'darkgreen', 'darkred', 'darkgrey', 'darkorange'])
224
+
225
+ ax.bar_label(bars)
226
+ ax = plt.gca().invert_yaxis()
227
+
228
+ st.subheader('Selected top products:')
229
+ st.pyplot(fig)
230
+
231
+ st.write("---")
232
+ #####################################################################################################################################
233
+
234
+ st.subheader('Barplot of sales:')
235
+ with st.expander('Select to choose the periodicity:'):
236
+ options_similar = ['Months', 'Days', 'Hours']
237
+ option_similar = st.selectbox('Choose a periodicity:', tuple(options_similar))
238
+
239
+ if option_similar == 'Months':
240
+ df_months = df_info.copy()
241
+ df_months = df_months.merge(pd.DataFrame(df_months.groupby('CustomerID')['Monetary'].sum()), on='CustomerID')
242
+ df_months['Periodicity'] = pd.DatetimeIndex(df_months['InvoiceDate']).month
243
+ df_months = df_months.sort_values('Recency')
244
+ df_months = df_months.drop_duplicates(subset='CustomerID')
245
+
246
+ fig1, ax1 = plt.subplots()
247
+ ax1 = sns.barplot(x=df_months['Periodicity'], y=df_months['Monetary_y'], errorbar=None)
248
+ plt.title('Sales per Months')
249
+ plt.xlabel('Periodicity: Months')
250
+ plt.ylabel('TotalOrderValue')
251
+ st.pyplot(fig1)
252
+
253
+ elif option_similar == 'Days':
254
+ df_days = df_info.copy()
255
+ df_days = df_days.merge(pd.DataFrame(df_days.groupby('CustomerID')['Monetary'].sum()), on='CustomerID')
256
+ df_days['Periodicity'] = pd.DatetimeIndex(df_days['InvoiceDate']).day
257
+ df_days = df_days.sort_values('Recency')
258
+ df_days = df_days.drop_duplicates(subset='CustomerID')
259
+
260
+ fig2, ax2 = plt.subplots()
261
+ ax2 = sns.barplot(x=df_days['Periodicity'], y=df_days['Monetary_y'], errorbar=None)
262
+ plt.title('Sales per Days')
263
+ plt.xlabel('Periodicity: Days')
264
+ plt.xticks(rotation=90)
265
+ plt.ylabel('TotalOrderValue')
266
+ st.pyplot(fig2)
267
+
268
+ elif option_similar == 'Hours':
269
+ df_hours = df_info.copy()
270
+ df_hours = df_hours.merge(pd.DataFrame(df_hours.groupby('CustomerID')['Monetary'].sum()), on='CustomerID')
271
+ df_hours['Periodicity'] = pd.DatetimeIndex(df_hours['InvoiceDate']).hour
272
+ df_hours = df_hours.sort_values('Recency')
273
+ df_hours = df_hours.drop_duplicates(subset='CustomerID')
274
+
275
+ fig3, ax3 = plt.subplots()
276
+ ax3 = sns.barplot(x=df_hours['Periodicity'], y=df_hours['Monetary_y'], errorbar=None)
277
+ plt.title('Sales per Hours')
278
+ plt.xlabel('Periodicity: Hours')
279
+ plt.ylabel('TotalOrderValue')
280
+ st.pyplot(fig3)
281
+
282
+
283
+ st.write("---")
284
+ #####################################################################################################################################
285
+
286
+ col1, col2, col3, col4, col5 = st.columns(5)
287
+ with col5:
288
+ logo_artefact = Image.open('static/logo_artefact.png')
289
+ st.image(logo_artefact)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ pandas
3
+ numpy
4
+ matplotlib
5
+ seaborn
6
+ datetime
7
+ Pillow
8
+ plotly
static/customer_info.csv ADDED
The diff for this file is too large to render. See raw diff
 
static/customer_segmentation.csv ADDED
The diff for this file is too large to render. See raw diff
 
static/logo_artefact.png ADDED
static/logo_random.png ADDED