Spaces:
Runtime error
Runtime error
HardWorkingStation
commited on
Commit
•
44e7893
1
Parent(s):
3a9a180
Initial commit
Browse files- src/test.ipynb +45 -112
- src/tools.py +95 -1
- src/web_app.py +79 -21
src/test.ipynb
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
"cells": [
|
3 |
{
|
4 |
"cell_type": "code",
|
5 |
-
"execution_count":
|
6 |
"metadata": {
|
7 |
"collapsed": true,
|
8 |
"pycharm": {
|
@@ -25,59 +25,20 @@
|
|
25 |
{
|
26 |
"cell_type": "code",
|
27 |
"execution_count": 2,
|
28 |
-
"outputs": [],
|
29 |
-
"source": [
|
30 |
-
"def get_data() -> tuple[Any, Any, Any]:\n",
|
31 |
-
"\t# получаем датасет\n",
|
32 |
-
"\tdataset = fetch_hillstrom(target_col='visit')\n",
|
33 |
-
"\tdataset, target, treatment = dataset['data'], dataset['target'], dataset['treatment']\n",
|
34 |
-
"\t# выбираем два сегмента\n",
|
35 |
-
"\tdataset = dataset[treatment != 'Mens E-Mail']\n",
|
36 |
-
"\ttarget = target[treatment != 'Mens E-Mail']\n",
|
37 |
-
"\ttreatment = treatment[treatment != 'Mens E-Mail'].map({\n",
|
38 |
-
"\t\t'Womens E-Mail': 1,\n",
|
39 |
-
"\t\t'No E-Mail': 0\n",
|
40 |
-
"\t})\n",
|
41 |
-
"\n",
|
42 |
-
"\treturn dataset, target, treatment"
|
43 |
-
],
|
44 |
-
"metadata": {
|
45 |
-
"collapsed": false,
|
46 |
-
"pycharm": {
|
47 |
-
"name": "#%%\n"
|
48 |
-
}
|
49 |
-
}
|
50 |
-
},
|
51 |
-
{
|
52 |
-
"cell_type": "code",
|
53 |
-
"execution_count": 3,
|
54 |
-
"outputs": [],
|
55 |
-
"source": [
|
56 |
-
"data, target, treatment = get_data()"
|
57 |
-
],
|
58 |
-
"metadata": {
|
59 |
-
"collapsed": false,
|
60 |
-
"pycharm": {
|
61 |
-
"name": "#%%\n"
|
62 |
-
}
|
63 |
-
}
|
64 |
-
},
|
65 |
-
{
|
66 |
-
"cell_type": "code",
|
67 |
-
"execution_count": 4,
|
68 |
"outputs": [
|
69 |
{
|
70 |
"data": {
|
71 |
-
"text/plain": "
|
72 |
-
"text/html": "<div>\n<style scoped>\n .dataframe tbody tr th:only-of-type {\n vertical-align: middle;\n }\n\n .dataframe tbody tr th {\n vertical-align: top;\n }\n\n .dataframe thead th {\n text-align: right;\n }\n</style>\n<table border=\"1\" class=\"dataframe\">\n <thead>\n <tr style=\"text-align: right;\">\n <th></th>\n <th>recency</th>\n <th>history_segment</th>\n <th>history</th>\n <th>mens</th>\n <th>womens</th>\n <th>zip_code</th>\n <th>newbie</th>\n <th>channel</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>0</th>\n <td>10</td>\n <td>2) $100 - $200</td>\n <td>142.44</td>\n <td>1</td>\n <td>0</td>\n <td>Surburban</td>\n <td>0</td>\n <td>Phone</td>\n </tr>\n <tr>\n <th>1</th>\n <td>6</td>\n <td>3) $200 - $350</td>\n <td>329.08</td>\n <td>1</td>\n <td>1</td>\n <td>Rural</td>\n <td>1</td>\n <td>Web</td>\n </tr>\n <tr>\n <th>2</th>\n <td>7</td>\n <td>2) $100 - $200</td>\n <td>180.65</td>\n <td>0</td>\n <td>1</td>\n <td>Surburban</td>\n <td>1</td>\n <td>Web</td>\n </tr>\n <tr>\n <th>4</th>\n <td>2</td>\n <td>1) $0 - $100</td>\n <td>45.34</td>\n <td>1</td>\n <td>0</td>\n <td>Urban</td>\n <td>0</td>\n <td>Web</td>\n </tr>\n <tr>\n <th>5</th>\n <td>6</td>\n <td>2) $100 - $200</td>\n <td>134.83</td>\n <td>0</td>\n <td>1</td>\n <td>Surburban</td>\n <td>0</td>\n <td>Phone</td>\n </tr>\n </tbody>\n</table>\n</div>"
|
73 |
},
|
74 |
-
"execution_count":
|
75 |
"metadata": {},
|
76 |
"output_type": "execute_result"
|
77 |
}
|
78 |
],
|
79 |
"source": [
|
80 |
-
"
|
|
|
|
|
81 |
],
|
82 |
"metadata": {
|
83 |
"collapsed": false,
|
@@ -88,19 +49,22 @@
|
|
88 |
},
|
89 |
{
|
90 |
"cell_type": "code",
|
91 |
-
"execution_count":
|
92 |
-
"outputs": [
|
93 |
-
{
|
94 |
-
"data": {
|
95 |
-
"text/plain": "0 0\n1 0\n2 0\n4 0\n5 1\nName: visit, dtype: int64"
|
96 |
-
},
|
97 |
-
"execution_count": 5,
|
98 |
-
"metadata": {},
|
99 |
-
"output_type": "execute_result"
|
100 |
-
}
|
101 |
-
],
|
102 |
"source": [
|
103 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
],
|
105 |
"metadata": {
|
106 |
"collapsed": false,
|
@@ -111,19 +75,10 @@
|
|
111 |
},
|
112 |
{
|
113 |
"cell_type": "code",
|
114 |
-
"execution_count":
|
115 |
-
"outputs": [
|
116 |
-
{
|
117 |
-
"data": {
|
118 |
-
"text/plain": "0 1\n1 0\n2 1\n4 1\n5 1\nName: segment, dtype: int64"
|
119 |
-
},
|
120 |
-
"execution_count": 6,
|
121 |
-
"metadata": {},
|
122 |
-
"output_type": "execute_result"
|
123 |
-
}
|
124 |
-
],
|
125 |
"source": [
|
126 |
-
"treatment
|
127 |
],
|
128 |
"metadata": {
|
129 |
"collapsed": false,
|
@@ -134,20 +89,17 @@
|
|
134 |
},
|
135 |
{
|
136 |
"cell_type": "code",
|
137 |
-
"execution_count":
|
138 |
-
"outputs": [
|
139 |
-
{
|
140 |
-
"data": {
|
141 |
-
"text/plain": "visit 0 1\nsegment \n0 0.893833 0.106167\n1 0.848600 0.151400",
|
142 |
-
"text/html": "<div>\n<style scoped>\n .dataframe tbody tr th:only-of-type {\n vertical-align: middle;\n }\n\n .dataframe tbody tr th {\n vertical-align: top;\n }\n\n .dataframe thead th {\n text-align: right;\n }\n</style>\n<table border=\"1\" class=\"dataframe\">\n <thead>\n <tr style=\"text-align: right;\">\n <th>visit</th>\n <th>0</th>\n <th>1</th>\n </tr>\n <tr>\n <th>segment</th>\n <th></th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>0</th>\n <td>0.893833</td>\n <td>0.106167</td>\n </tr>\n <tr>\n <th>1</th>\n <td>0.848600</td>\n <td>0.151400</td>\n </tr>\n </tbody>\n</table>\n</div>"
|
143 |
-
},
|
144 |
-
"execution_count": 23,
|
145 |
-
"metadata": {},
|
146 |
-
"output_type": "execute_result"
|
147 |
-
}
|
148 |
-
],
|
149 |
"source": [
|
150 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
],
|
152 |
"metadata": {
|
153 |
"collapsed": false,
|
@@ -158,19 +110,20 @@
|
|
158 |
},
|
159 |
{
|
160 |
"cell_type": "code",
|
161 |
-
"execution_count":
|
162 |
"outputs": [
|
163 |
{
|
164 |
"data": {
|
165 |
-
"text/plain": "1
|
|
|
166 |
},
|
|
|
167 |
"metadata": {},
|
168 |
-
"output_type": "
|
169 |
}
|
170 |
],
|
171 |
"source": [
|
172 |
-
"
|
173 |
-
"display(res)"
|
174 |
],
|
175 |
"metadata": {
|
176 |
"collapsed": false,
|
@@ -181,40 +134,20 @@
|
|
181 |
},
|
182 |
{
|
183 |
"cell_type": "code",
|
184 |
-
"execution_count":
|
185 |
"outputs": [
|
186 |
{
|
187 |
"data": {
|
188 |
-
"text/plain": "Surburban
|
|
|
189 |
},
|
190 |
-
"execution_count":
|
191 |
"metadata": {},
|
192 |
"output_type": "execute_result"
|
193 |
}
|
194 |
],
|
195 |
"source": [
|
196 |
-
"
|
197 |
-
],
|
198 |
-
"metadata": {
|
199 |
-
"collapsed": false,
|
200 |
-
"pycharm": {
|
201 |
-
"name": "#%%\n"
|
202 |
-
}
|
203 |
-
}
|
204 |
-
},
|
205 |
-
{
|
206 |
-
"cell_type": "code",
|
207 |
-
"execution_count": 12,
|
208 |
-
"outputs": [],
|
209 |
-
"source": [
|
210 |
-
"X_train, X_val, y_train, y_val, trmnt_train, trmnt_val = train_test_split(\n",
|
211 |
-
" data, target, treatment, test_size=0.3, random_state=42\n",
|
212 |
-
")\n",
|
213 |
-
"\n",
|
214 |
-
"models_results = {\n",
|
215 |
-
" 'approach': [],\n",
|
216 |
-
" 'uplift@30%': []\n",
|
217 |
-
"}"
|
218 |
],
|
219 |
"metadata": {
|
220 |
"collapsed": false,
|
|
|
2 |
"cells": [
|
3 |
{
|
4 |
"cell_type": "code",
|
5 |
+
"execution_count": 1,
|
6 |
"metadata": {
|
7 |
"collapsed": true,
|
8 |
"pycharm": {
|
|
|
25 |
{
|
26 |
"cell_type": "code",
|
27 |
"execution_count": 2,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
"outputs": [
|
29 |
{
|
30 |
"data": {
|
31 |
+
"text/plain": "{'new_filter': 'filter'}"
|
|
|
32 |
},
|
33 |
+
"execution_count": 2,
|
34 |
"metadata": {},
|
35 |
"output_type": "execute_result"
|
36 |
}
|
37 |
],
|
38 |
"source": [
|
39 |
+
"filters = {}\n",
|
40 |
+
"filters['new_filter'] = 'filter'\n",
|
41 |
+
"filters"
|
42 |
],
|
43 |
"metadata": {
|
44 |
"collapsed": false,
|
|
|
49 |
},
|
50 |
{
|
51 |
"cell_type": "code",
|
52 |
+
"execution_count": 4,
|
53 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
"source": [
|
55 |
+
"def get_data() -> tuple[Any, Any, Any]:\n",
|
56 |
+
"\t# получаем датасет\n",
|
57 |
+
"\tdataset = fetch_hillstrom(target_col='visit')\n",
|
58 |
+
"\tdataset, target, treatment = dataset['data'], dataset['target'], dataset['treatment']\n",
|
59 |
+
"\t# выбираем два сегмента\n",
|
60 |
+
"\tdataset = dataset[treatment != 'Mens E-Mail']\n",
|
61 |
+
"\ttarget = target[treatment != 'Mens E-Mail']\n",
|
62 |
+
"\ttreatment = treatment[treatment != 'Mens E-Mail'].map({\n",
|
63 |
+
"\t\t'Womens E-Mail': 1,\n",
|
64 |
+
"\t\t'No E-Mail': 0\n",
|
65 |
+
"\t})\n",
|
66 |
+
"\n",
|
67 |
+
"\treturn dataset, target, treatment"
|
68 |
],
|
69 |
"metadata": {
|
70 |
"collapsed": false,
|
|
|
75 |
},
|
76 |
{
|
77 |
"cell_type": "code",
|
78 |
+
"execution_count": 111,
|
79 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
"source": [
|
81 |
+
"data, target, treatment = get_data()"
|
82 |
],
|
83 |
"metadata": {
|
84 |
"collapsed": false,
|
|
|
89 |
},
|
90 |
{
|
91 |
"cell_type": "code",
|
92 |
+
"execution_count": 112,
|
93 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
"source": [
|
95 |
+
"X_train, X_val, y_train, y_val, trmnt_train, trmnt_val = train_test_split(\n",
|
96 |
+
" data, target, treatment, test_size=0.3, random_state=42\n",
|
97 |
+
")\n",
|
98 |
+
"\n",
|
99 |
+
"models_results = {\n",
|
100 |
+
" 'approach': [],\n",
|
101 |
+
" 'uplift@30%': []\n",
|
102 |
+
"}"
|
103 |
],
|
104 |
"metadata": {
|
105 |
"collapsed": false,
|
|
|
110 |
},
|
111 |
{
|
112 |
"cell_type": "code",
|
113 |
+
"execution_count": 113,
|
114 |
"outputs": [
|
115 |
{
|
116 |
"data": {
|
117 |
+
"text/plain": " recency history_segment history mens womens zip_code newbie \\\n609 9 3) $200 - $350 212.32 1 0 Surburban 0 \n51952 3 6) $750 - $1,000 849.16 1 1 Surburban 1 \n33629 9 1) $0 - $100 43.22 1 0 Surburban 1 \n22103 8 1) $0 - $100 29.99 0 1 Surburban 0 \n21350 7 2) $100 - $200 164.07 1 0 Surburban 1 \n... ... ... ... ... ... ... ... \n9307 1 2) $100 - $200 110.45 0 1 Urban 0 \n16819 1 1) $0 - $100 88.04 0 1 Surburban 0 \n57173 1 1) $0 - $100 72.63 0 1 Rural 0 \n1282 3 4) $350 - $500 366.16 0 1 Rural 0 \n23624 9 1) $0 - $100 36.87 1 0 Urban 1 \n\n channel \n609 Multichannel \n51952 Multichannel \n33629 Phone \n22103 Web \n21350 Phone \n... ... \n9307 Phone \n16819 Phone \n57173 Phone \n1282 Phone \n23624 Web \n\n[29885 rows x 8 columns]",
|
118 |
+
"text/html": "<div>\n<style scoped>\n .dataframe tbody tr th:only-of-type {\n vertical-align: middle;\n }\n\n .dataframe tbody tr th {\n vertical-align: top;\n }\n\n .dataframe thead th {\n text-align: right;\n }\n</style>\n<table border=\"1\" class=\"dataframe\">\n <thead>\n <tr style=\"text-align: right;\">\n <th></th>\n <th>recency</th>\n <th>history_segment</th>\n <th>history</th>\n <th>mens</th>\n <th>womens</th>\n <th>zip_code</th>\n <th>newbie</th>\n <th>channel</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>609</th>\n <td>9</td>\n <td>3) $200 - $350</td>\n <td>212.32</td>\n <td>1</td>\n <td>0</td>\n <td>Surburban</td>\n <td>0</td>\n <td>Multichannel</td>\n </tr>\n <tr>\n <th>51952</th>\n <td>3</td>\n <td>6) $750 - $1,000</td>\n <td>849.16</td>\n <td>1</td>\n <td>1</td>\n <td>Surburban</td>\n <td>1</td>\n <td>Multichannel</td>\n </tr>\n <tr>\n <th>33629</th>\n <td>9</td>\n <td>1) $0 - $100</td>\n <td>43.22</td>\n <td>1</td>\n <td>0</td>\n <td>Surburban</td>\n <td>1</td>\n <td>Phone</td>\n </tr>\n <tr>\n <th>22103</th>\n <td>8</td>\n <td>1) $0 - $100</td>\n <td>29.99</td>\n <td>0</td>\n <td>1</td>\n <td>Surburban</td>\n <td>0</td>\n <td>Web</td>\n </tr>\n <tr>\n <th>21350</th>\n <td>7</td>\n <td>2) $100 - $200</td>\n <td>164.07</td>\n <td>1</td>\n <td>0</td>\n <td>Surburban</td>\n <td>1</td>\n <td>Phone</td>\n </tr>\n <tr>\n <th>...</th>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n </tr>\n <tr>\n <th>9307</th>\n <td>1</td>\n <td>2) $100 - $200</td>\n <td>110.45</td>\n <td>0</td>\n <td>1</td>\n <td>Urban</td>\n <td>0</td>\n <td>Phone</td>\n </tr>\n <tr>\n <th>16819</th>\n <td>1</td>\n <td>1) $0 - $100</td>\n <td>88.04</td>\n <td>0</td>\n <td>1</td>\n <td>Surburban</td>\n <td>0</td>\n <td>Phone</td>\n </tr>\n <tr>\n <th>57173</th>\n <td>1</td>\n <td>1) $0 - $100</td>\n <td>72.63</td>\n <td>0</td>\n <td>1</td>\n <td>Rural</td>\n <td>0</td>\n <td>Phone</td>\n </tr>\n <tr>\n <th>1282</th>\n <td>3</td>\n <td>4) $350 - $500</td>\n <td>366.16</td>\n <td>0</td>\n <td>1</td>\n <td>Rural</td>\n <td>0</td>\n <td>Phone</td>\n </tr>\n <tr>\n <th>23624</th>\n <td>9</td>\n <td>1) $0 - $100</td>\n <td>36.87</td>\n <td>1</td>\n <td>0</td>\n <td>Urban</td>\n <td>1</td>\n <td>Web</td>\n </tr>\n </tbody>\n</table>\n<p>29885 rows × 8 columns</p>\n</div>"
|
119 |
},
|
120 |
+
"execution_count": 113,
|
121 |
"metadata": {},
|
122 |
+
"output_type": "execute_result"
|
123 |
}
|
124 |
],
|
125 |
"source": [
|
126 |
+
"X_train"
|
|
|
127 |
],
|
128 |
"metadata": {
|
129 |
"collapsed": false,
|
|
|
134 |
},
|
135 |
{
|
136 |
"cell_type": "code",
|
137 |
+
"execution_count": 114,
|
138 |
"outputs": [
|
139 |
{
|
140 |
"data": {
|
141 |
+
"text/plain": " recency history_segment history mens womens zip_code newbie \\\n7730 3 5) $500 - $750 503.73 0 1 Rural 1 \n17594 5 2) $100 - $200 163.39 1 0 Urban 0 \n14481 11 3) $200 - $350 287.77 1 0 Rural 1 \n20003 3 1) $0 - $100 29.99 0 1 Surburban 1 \n19981 5 2) $100 - $200 131.51 0 1 Urban 1 \n... ... ... ... ... ... ... ... \n41828 11 4) $350 - $500 457.09 0 1 Surburban 1 \n49354 2 1) $0 - $100 29.99 0 1 Rural 1 \n12063 9 2) $100 - $200 147.48 1 0 Rural 1 \n3757 10 2) $100 - $200 128.78 1 0 Surburban 1 \n10708 6 2) $100 - $200 138.72 1 0 Urban 0 \n\n channel \n7730 Multichannel \n17594 Web \n14481 Web \n20003 Web \n19981 Web \n... ... \n41828 Multichannel \n49354 Phone \n12063 Phone \n3757 Phone \n10708 Web \n\n[12808 rows x 8 columns]",
|
142 |
+
"text/html": "<div>\n<style scoped>\n .dataframe tbody tr th:only-of-type {\n vertical-align: middle;\n }\n\n .dataframe tbody tr th {\n vertical-align: top;\n }\n\n .dataframe thead th {\n text-align: right;\n }\n</style>\n<table border=\"1\" class=\"dataframe\">\n <thead>\n <tr style=\"text-align: right;\">\n <th></th>\n <th>recency</th>\n <th>history_segment</th>\n <th>history</th>\n <th>mens</th>\n <th>womens</th>\n <th>zip_code</th>\n <th>newbie</th>\n <th>channel</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>7730</th>\n <td>3</td>\n <td>5) $500 - $750</td>\n <td>503.73</td>\n <td>0</td>\n <td>1</td>\n <td>Rural</td>\n <td>1</td>\n <td>Multichannel</td>\n </tr>\n <tr>\n <th>17594</th>\n <td>5</td>\n <td>2) $100 - $200</td>\n <td>163.39</td>\n <td>1</td>\n <td>0</td>\n <td>Urban</td>\n <td>0</td>\n <td>Web</td>\n </tr>\n <tr>\n <th>14481</th>\n <td>11</td>\n <td>3) $200 - $350</td>\n <td>287.77</td>\n <td>1</td>\n <td>0</td>\n <td>Rural</td>\n <td>1</td>\n <td>Web</td>\n </tr>\n <tr>\n <th>20003</th>\n <td>3</td>\n <td>1) $0 - $100</td>\n <td>29.99</td>\n <td>0</td>\n <td>1</td>\n <td>Surburban</td>\n <td>1</td>\n <td>Web</td>\n </tr>\n <tr>\n <th>19981</th>\n <td>5</td>\n <td>2) $100 - $200</td>\n <td>131.51</td>\n <td>0</td>\n <td>1</td>\n <td>Urban</td>\n <td>1</td>\n <td>Web</td>\n </tr>\n <tr>\n <th>...</th>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n </tr>\n <tr>\n <th>41828</th>\n <td>11</td>\n <td>4) $350 - $500</td>\n <td>457.09</td>\n <td>0</td>\n <td>1</td>\n <td>Surburban</td>\n <td>1</td>\n <td>Multichannel</td>\n </tr>\n <tr>\n <th>49354</th>\n <td>2</td>\n <td>1) $0 - $100</td>\n <td>29.99</td>\n <td>0</td>\n <td>1</td>\n <td>Rural</td>\n <td>1</td>\n <td>Phone</td>\n </tr>\n <tr>\n <th>12063</th>\n <td>9</td>\n <td>2) $100 - $200</td>\n <td>147.48</td>\n <td>1</td>\n <td>0</td>\n <td>Rural</td>\n <td>1</td>\n <td>Phone</td>\n </tr>\n <tr>\n <th>3757</th>\n <td>10</td>\n <td>2) $100 - $200</td>\n <td>128.78</td>\n <td>1</td>\n <td>0</td>\n <td>Surburban</td>\n <td>1</td>\n <td>Phone</td>\n </tr>\n <tr>\n <th>10708</th>\n <td>6</td>\n <td>2) $100 - $200</td>\n <td>138.72</td>\n <td>1</td>\n <td>0</td>\n <td>Urban</td>\n <td>0</td>\n <td>Web</td>\n </tr>\n </tbody>\n</table>\n<p>12808 rows × 8 columns</p>\n</div>"
|
143 |
},
|
144 |
+
"execution_count": 114,
|
145 |
"metadata": {},
|
146 |
"output_type": "execute_result"
|
147 |
}
|
148 |
],
|
149 |
"source": [
|
150 |
+
"X_val"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
],
|
152 |
"metadata": {
|
153 |
"collapsed": false,
|
src/tools.py
CHANGED
@@ -4,6 +4,7 @@ import pandas as pd
|
|
4 |
import numpy as np
|
5 |
from sklearn.model_selection import train_test_split
|
6 |
from sklift.datasets import fetch_hillstrom
|
|
|
7 |
from catboost import CatBoostClassifier
|
8 |
import sklearn
|
9 |
import streamlit as st
|
@@ -28,7 +29,7 @@ def get_data() -> tuple[Any, Any, Any]:
|
|
28 |
|
29 |
|
30 |
@st.experimental_memo
|
31 |
-
def data_split(data, treatment, target) -> tuple[Any, Any, Any, Any, Any, Any]:
|
32 |
# склеиваем threatment и target для дальнейшей стратификации по ним
|
33 |
stratify_cols = pd.concat([treatment, target], axis=1)
|
34 |
# сплитим датасет
|
@@ -43,6 +44,99 @@ def data_split(data, treatment, target) -> tuple[Any, Any, Any, Any, Any, Any]:
|
|
43 |
return X_train, X_val, trmnt_train, trmnt_val, y_train, y_val
|
44 |
|
45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
def get_newbie_plot(data):
|
47 |
fig = px.histogram(
|
48 |
data['newbie'],
|
|
|
4 |
import numpy as np
|
5 |
from sklearn.model_selection import train_test_split
|
6 |
from sklift.datasets import fetch_hillstrom
|
7 |
+
from sklift.metrics import uplift_at_k
|
8 |
from catboost import CatBoostClassifier
|
9 |
import sklearn
|
10 |
import streamlit as st
|
|
|
29 |
|
30 |
|
31 |
@st.experimental_memo
|
32 |
+
def data_split(data: pd.DataFrame, treatment: pd.DataFrame, target: pd.DataFrame) -> tuple[Any, Any, Any, Any, Any, Any]:
|
33 |
# склеиваем threatment и target для дальнейшей стратификации по ним
|
34 |
stratify_cols = pd.concat([treatment, target], axis=1)
|
35 |
# сплитим датасет
|
|
|
44 |
return X_train, X_val, trmnt_train, trmnt_val, y_train, y_val
|
45 |
|
46 |
|
47 |
+
def filter_by_newbie(data: pd.DataFrame, newbie_filter: str) -> pd.DataFrame:
|
48 |
+
if newbie_filter == 'Всем':
|
49 |
+
return data
|
50 |
+
elif newbie_filter == 'Только новым':
|
51 |
+
return data[data['newbie'] == 1]
|
52 |
+
elif newbie_filter == 'Только старым':
|
53 |
+
return data[data['newbie'] == 0]
|
54 |
+
|
55 |
+
|
56 |
+
def filter_by_channel(data: pd.DataFrame, channel_filter: str) -> pd.DataFrame:
|
57 |
+
if channel_filter == 'Всем':
|
58 |
+
return data
|
59 |
+
if channel_filter == 'Phone':
|
60 |
+
return data[data['channel'] == channel_filter]
|
61 |
+
if channel_filter == 'Web':
|
62 |
+
return data[data['channel'] == channel_filter]
|
63 |
+
if channel_filter == 'Multichannel':
|
64 |
+
return data[data['channel'] == channel_filter]
|
65 |
+
|
66 |
+
|
67 |
+
def filter_by_mens(data: pd.DataFrame, mens_filter: str) -> pd.DataFrame:
|
68 |
+
if mens_filter == 'Любые товары':
|
69 |
+
return data
|
70 |
+
if mens_filter == 'Мужские':
|
71 |
+
return data[data['mens'] == 1]
|
72 |
+
if mens_filter == 'Женские':
|
73 |
+
return data[data['womens'] == 1]
|
74 |
+
|
75 |
+
|
76 |
+
def filter_by_history_segments(data: pd.DataFrame, history_segments_filter: dict) -> pd.DataFrame:
|
77 |
+
filtered_indexes = set()
|
78 |
+
if history_segments_filter.get('1) $0 - $100'):
|
79 |
+
filtered_indexes = filtered_indexes.union(data[data['history_segment'] == '1) $0 - $100'].index)
|
80 |
+
if history_segments_filter.get('2) $100 - $200'):
|
81 |
+
filtered_indexes = filtered_indexes.union(data[data['history_segment'] == '2) $100 - $200'].index)
|
82 |
+
if history_segments_filter.get('3) $200 - $350'):
|
83 |
+
filtered_indexes = filtered_indexes.union(data[data['history_segment'] == '3) $200 - $350'].index)
|
84 |
+
if history_segments_filter.get('4) $350 - $500'):
|
85 |
+
filtered_indexes = filtered_indexes.union(data[data['history_segment'] == '4) $350 - $500'].index)
|
86 |
+
if history_segments_filter.get('5) $500 - $750'):
|
87 |
+
filtered_indexes = filtered_indexes.union(data[data['history_segment'] == '5) $500 - $750'].index)
|
88 |
+
if history_segments_filter.get('6) $750 - $1,000'):
|
89 |
+
filtered_indexes = filtered_indexes.union(data[data['history_segment'] == '6) $750 - $1,000'].index)
|
90 |
+
if history_segments_filter.get('7) $1,000 +'):
|
91 |
+
filtered_indexes = filtered_indexes.union(data[data['history_segment'] == '7) $1,000 +'].index)
|
92 |
+
|
93 |
+
return data.loc[list(filtered_indexes)]
|
94 |
+
|
95 |
+
|
96 |
+
def filter_by_zip_code(data: pd.DataFrame, zip_code_filter: dict) -> pd.DataFrame:
|
97 |
+
filterd_indexes = set()
|
98 |
+
if zip_code_filter.get('surburban'):
|
99 |
+
filterd_indexes = filterd_indexes.union(data[data['zip_code'] == 'Surburban'].index)
|
100 |
+
if zip_code_filter.get('urban'):
|
101 |
+
filterd_indexes = filterd_indexes.union(data[data['zip_code'] == 'Urban'].index)
|
102 |
+
if zip_code_filter.get('rural'):
|
103 |
+
filterd_indexes = filterd_indexes.union(data[data['zip_code'] == 'Rural'].index)
|
104 |
+
|
105 |
+
return data.loc[list(filterd_indexes)]
|
106 |
+
|
107 |
+
|
108 |
+
def filter_by_recency(data: pd.DataFrame, recency_filter: list) -> pd.DataFrame:
|
109 |
+
return data[(data['recency'] >= recency_filter[0]) & (data['recency'] <= recency_filter[1])]
|
110 |
+
|
111 |
+
|
112 |
+
def filter_data(data: pd.DataFrame, filters: dict) -> pd.DataFrame or None:
|
113 |
+
data = filter_by_newbie(data, filters['newbie_filter'])
|
114 |
+
if data.shape[0] == 0:
|
115 |
+
return None
|
116 |
+
data = filter_by_channel(data, filters['channel_filter'])
|
117 |
+
if data.shape[0] == 0:
|
118 |
+
return None
|
119 |
+
data = filter_by_mens(data, filters['mens_filter'])
|
120 |
+
if data.shape[0] == 0:
|
121 |
+
return None
|
122 |
+
data = filter_by_history_segments(data, filters['history_segments'])
|
123 |
+
if data.shape[0] == 0:
|
124 |
+
return None
|
125 |
+
data = filter_by_zip_code(data, filters['zip_code'])
|
126 |
+
if data.shape[0] == 0:
|
127 |
+
return None
|
128 |
+
data = filter_by_recency(data, filters['recency'])
|
129 |
+
if data.shape[0] == 0:
|
130 |
+
return None
|
131 |
+
return data
|
132 |
+
|
133 |
+
|
134 |
+
def send_promo_and_get_res(data_train: pd.DataFrame, treatment: pd.DataFrame, target: pd.DataFrame):
|
135 |
+
indexes = data.index
|
136 |
+
target = target.loc[indexes]
|
137 |
+
treatment = treatment.loc[indexes]
|
138 |
+
|
139 |
+
|
140 |
def get_newbie_plot(data):
|
141 |
fig = px.histogram(
|
142 |
data['newbie'],
|
src/web_app.py
CHANGED
@@ -7,6 +7,8 @@ import tools
|
|
7 |
|
8 |
dataset, target, treatment = tools.get_data()
|
9 |
|
|
|
|
|
10 |
st.title('Uplift lab')
|
11 |
|
12 |
st.markdown(
|
@@ -17,6 +19,8 @@ st.markdown(
|
|
17 |
|
18 |
Этот набор данных содержит 42 693 строк с данными клиентов, которые в последний раз совершали покупки в течение двенадцати месяцев.
|
19 |
|
|
|
|
|
20 |
Среди клиентов была проведена рекламная кампания с помощью _email_ рассылки:
|
21 |
- 1/2 клиентов были выбраны случайным образом для получения электронного письма, рекламирующего женскую продукцию;
|
22 |
- С оставшейся 1/2 коммуникацию не проводили.
|
@@ -28,11 +32,11 @@ st.markdown(
|
|
28 |
"""
|
29 |
)
|
30 |
refresh = st.button('Обновить выборку')
|
31 |
-
title_subsample =
|
32 |
if refresh:
|
33 |
-
title_subsample =
|
34 |
st.dataframe(title_subsample, width=700)
|
35 |
-
st.write(
|
36 |
|
37 |
st.write('Описание данных')
|
38 |
st.markdown(
|
@@ -56,59 +60,113 @@ st.write("Для того, чтобы лучше понять на какую а
|
|
56 |
|
57 |
with st.expander('Развернуть блок анализа данных'):
|
58 |
|
59 |
-
st.plotly_chart(tools.get_newbie_plot(
|
60 |
st.write(f'В данных примерно одинаковое количество новых и "старых клиентов". '
|
61 |
-
f'Отношение новых клиентов к старым: {(
|
62 |
|
63 |
-
st.plotly_chart(tools.get_zipcode_plot(
|
64 |
-
tmp_res =
|
65 |
st.write(f'Большинство клиентов из пригорода: {tmp_res["Surburban"]:.2f}%, из города: {tmp_res["Urban"]:.2f}% и из села: {tmp_res["Rural"]:.2f}%')
|
66 |
|
67 |
-
tmp_res =
|
68 |
-
st.plotly_chart(tools.get_channel_plot(
|
69 |
st.write(f'В прошлом году почти одинаковое количество клиентов покупало товары через телефон и сайт, {tmp_res["Phone"]:.2f}% и {tmp_res["Web"]:.2f}% соответственно,'
|
70 |
f' а {tmp_res["Multichannel"]:.2f}% клиентов покупали товары воспользовавшись двумя платформами.')
|
71 |
|
72 |
-
tmp_res =
|
73 |
-
st.plotly_chart(tools.get_history_segment_plot(
|
74 |
st.write(f'Как мы видим, большинство пользователей относится к сегменту \$0-\$100 ({tmp_res[0]:.2f}%), второй и '
|
75 |
f'третий по количеству пользователей сегменты \$100-\$200 ({tmp_res[1]:.2f}%) и \$200-\$350 ({tmp_res[2]:.2f}%).')
|
76 |
st.write(f'К сегментам \$350-\$500 и \$500-\$750 относится {tmp_res[3]:.2f}% и {tmp_res[4]:.2f}% пользователей соответственно.')
|
77 |
st.write(f'Меньше всего пользователей в сегментах \$750-\$1.000 ({tmp_res[-2]:.2f}%) и \$1.000+ ({tmp_res[-1]:.2f}%).')
|
78 |
|
79 |
-
tmp_res = list(
|
80 |
-
st.plotly_chart(tools.get_recency_plot(
|
81 |
st.write(f'Большинство клиентов являются активными клиентами платформы, и совершали покупки в течен��е месяца ({tmp_res[0]:.2f}%)')
|
82 |
st.write('Также заметно, что 9 и 10 месяцев назад, много клиентов совершали покупки. Это может свидетельствовать о проведении'
|
83 |
'рекламной кампании в это время или чего-то еще.')
|
84 |
st.write('Также интересно понаблюдать за долями новых клиентов в данном распределении.')
|
85 |
|
86 |
-
st.plotly_chart(tools.get_history_plot(
|
87 |
-
st.markdown('_График
|
88 |
st.write('Абсолютное большинство клиентов тратят \$25-\$35 на покупки, но есть и малая доля тех, кто тратит более \$3.000')
|
89 |
st.write('Интересный факт: все покупки более \$500 совершают только новые клиенты')
|
90 |
|
91 |
-
|
92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
|
|
|
|
|
94 |
|
95 |
-
st.write('Выберите класс клиентов, по объему денег, потраченных в прошлом году (history segments)
|
|
|
96 |
first_group = st.checkbox('$0-$100', value=True)
|
|
|
|
|
97 |
second_group = st.checkbox('$100-$200', value=True)
|
|
|
|
|
98 |
third_group = st.checkbox('$200-$350', value=True)
|
|
|
|
|
99 |
fourth_group = st.checkbox('$350-$500', value=True)
|
|
|
|
|
100 |
fifth_group = st.checkbox('$500-$750', value=True)
|
|
|
|
|
101 |
sixth_group = st.checkbox('$750-$1.000', value=True)
|
|
|
|
|
102 |
seventh_group = st.checkbox('$1.000+', value=True)
|
|
|
|
|
103 |
|
104 |
st.write('Каких пользователей по почтовому коду выберем')
|
|
|
105 |
surburban = st.checkbox('Surburban', value=True)
|
|
|
|
|
106 |
urban = st.checkbox('Urban', value=True)
|
|
|
|
|
107 |
rural = st.checkbox('Rural', value=True)
|
|
|
|
|
108 |
|
109 |
-
recency = st.slider(label='Месяцев с момента покупки', min_value=int(
|
|
|
110 |
|
|
|
111 |
if not first_group and not second_group and not third_group and not fourth_group and not fifth_group and not sixth_group and not seventh_group:
|
112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
else:
|
114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
|
8 |
dataset, target, treatment = tools.get_data()
|
9 |
|
10 |
+
data_train, data_val, treatment_train, treatment_val, target_train, target_val = tools.data_split(dataset, treatment, target)
|
11 |
+
|
12 |
st.title('Uplift lab')
|
13 |
|
14 |
st.markdown(
|
|
|
19 |
|
20 |
Этот набор данных содержит 42 693 строк с данными клиентов, которые в последний раз совершали покупки в течение двенадцати месяцев.
|
21 |
|
22 |
+
Из данных уже отделена тестовая выборка в виде 30% записей клиентов, так что данных в предоставленной выборке будет меньше.
|
23 |
+
|
24 |
Среди клиентов была проведена рекламная кампания с помощью _email_ рассылки:
|
25 |
- 1/2 клиентов были выбраны случайным образом для получения электронного письма, рекламирующего женскую продукцию;
|
26 |
- С оставшейся 1/2 коммуникацию не проводили.
|
|
|
32 |
"""
|
33 |
)
|
34 |
refresh = st.button('Обновить выборку')
|
35 |
+
title_subsample = data_train.sample(7)
|
36 |
if refresh:
|
37 |
+
title_subsample = data_train.sample(7)
|
38 |
st.dataframe(title_subsample, width=700)
|
39 |
+
st.write(f"Всего записей: {data_train.shape[0]}")
|
40 |
|
41 |
st.write('Описание данных')
|
42 |
st.markdown(
|
|
|
60 |
|
61 |
with st.expander('Развернуть блок анализа данных'):
|
62 |
|
63 |
+
st.plotly_chart(tools.get_newbie_plot(data_train), use_container_width=True)
|
64 |
st.write(f'В данных примерно одинаковое количество новых и "старых клиентов". '
|
65 |
+
f'Отношение новых клиентов к старым: {(data_train["newbie"] == 1).sum() / (data_train["newbie"] == 0).sum():.2f}')
|
66 |
|
67 |
+
st.plotly_chart(tools.get_zipcode_plot(data_train), use_container_width=True)
|
68 |
+
tmp_res = data_train.zip_code.value_counts(normalize=True) * 100
|
69 |
st.write(f'Большинство клиентов из пригорода: {tmp_res["Surburban"]:.2f}%, из города: {tmp_res["Urban"]:.2f}% и из села: {tmp_res["Rural"]:.2f}%')
|
70 |
|
71 |
+
tmp_res = data_train.channel.value_counts(normalize=True) * 100
|
72 |
+
st.plotly_chart(tools.get_channel_plot(data_train), use_container_width=True)
|
73 |
st.write(f'В прошлом году почти одинаковое количество клиентов покупало товары через телефон и сайт, {tmp_res["Phone"]:.2f}% и {tmp_res["Web"]:.2f}% соответственно,'
|
74 |
f' а {tmp_res["Multichannel"]:.2f}% клиентов покупали товары воспользовавшись двумя платформами.')
|
75 |
|
76 |
+
tmp_res = data_train.history_segment.value_counts(normalize=True) * 100
|
77 |
+
st.plotly_chart(tools.get_history_segment_plot(data_train), use_container_width=True)
|
78 |
st.write(f'Как мы видим, большинство пользователей относится к сегменту \$0-\$100 ({tmp_res[0]:.2f}%), второй и '
|
79 |
f'третий по количеству пользователей сегменты \$100-\$200 ({tmp_res[1]:.2f}%) и \$200-\$350 ({tmp_res[2]:.2f}%).')
|
80 |
st.write(f'К сегментам \$350-\$500 и \$500-\$750 относится {tmp_res[3]:.2f}% и {tmp_res[4]:.2f}% пользователей соответственно.')
|
81 |
st.write(f'Меньше всего пользователей в сегментах \$750-\$1.000 ({tmp_res[-2]:.2f}%) и \$1.000+ ({tmp_res[-1]:.2f}%).')
|
82 |
|
83 |
+
tmp_res = list(data_train.recency.value_counts(normalize=True) * 100)
|
84 |
+
st.plotly_chart(tools.get_recency_plot(data_train), use_container_width=True)
|
85 |
st.write(f'Большинство клиентов являются активными клиентами платформы, и совершали покупки в течен��е месяца ({tmp_res[0]:.2f}%)')
|
86 |
st.write('Также заметно, что 9 и 10 месяцев назад, много клиентов совершали покупки. Это может свидетельствовать о проведении'
|
87 |
'рекламной кампании в это время или чего-то еще.')
|
88 |
st.write('Также интересно понаблюдать за долями новых клиентов в данном распределении.')
|
89 |
|
90 |
+
st.plotly_chart(tools.get_history_plot(data_train), use_container_width=True)
|
91 |
+
st.markdown('_График интерактивный. Двойной клик вернет в начальное состояние._')
|
92 |
st.write('Абсолютное большинство клиентов тратят \$25-\$35 на покупки, но есть и малая доля тех, кто тратит более \$3.000')
|
93 |
st.write('Интересный факт: все покупки более \$500 совершают только новые клиенты')
|
94 |
|
95 |
+
filters = {}
|
96 |
+
|
97 |
+
st.subheader('Выберем клиентов, которым отправим рекламу.')
|
98 |
+
newbie_filter = st.radio('Каким клиентам отправим рекламу?', options=['Всем', 'Только новым', 'Только старым'])
|
99 |
+
filters['newbie_filter'] = newbie_filter
|
100 |
+
|
101 |
+
channel_filter = st.radio('Канал, по которому клиент покупал в прошлом году', options=['Всем', 'Phone', 'Web', 'Multichannel'])
|
102 |
+
filters['channel_filter'] = channel_filter
|
103 |
|
104 |
+
mens_filter = st.radio('Клиенты, приобретавшие', options=['Любые товары', 'Мужские', 'Женские'])
|
105 |
+
filters['mens_filter'] = mens_filter
|
106 |
|
107 |
+
st.write('Выберите класс клиентов, по объему денег, потраченных в прошлом году (history segments)')
|
108 |
+
filters['history_segments'] = {}
|
109 |
first_group = st.checkbox('$0-$100', value=True)
|
110 |
+
if first_group:
|
111 |
+
filters['history_segments']['1) $0 - $100'] = True
|
112 |
second_group = st.checkbox('$100-$200', value=True)
|
113 |
+
if second_group:
|
114 |
+
filters['history_segments']['2) $100 - $200'] = True
|
115 |
third_group = st.checkbox('$200-$350', value=True)
|
116 |
+
if third_group:
|
117 |
+
filters['history_segments']['3) $200 - $350'] = True
|
118 |
fourth_group = st.checkbox('$350-$500', value=True)
|
119 |
+
if fourth_group:
|
120 |
+
filters['history_segments']['4) $350 - $500'] = True
|
121 |
fifth_group = st.checkbox('$500-$750', value=True)
|
122 |
+
if fifth_group:
|
123 |
+
filters['history_segments']['5) $500 - $750'] = True
|
124 |
sixth_group = st.checkbox('$750-$1.000', value=True)
|
125 |
+
if sixth_group:
|
126 |
+
filters['history_segments']['6) $750 - $1,000'] = True
|
127 |
seventh_group = st.checkbox('$1.000+', value=True)
|
128 |
+
if seventh_group:
|
129 |
+
filters['history_segments']['7) $1,000 +'] = True
|
130 |
|
131 |
st.write('Каких пользователей по почтовому коду выберем')
|
132 |
+
filters['zip_code'] = {}
|
133 |
surburban = st.checkbox('Surburban', value=True)
|
134 |
+
if surburban:
|
135 |
+
filters['zip_code']['surburban'] = True
|
136 |
urban = st.checkbox('Urban', value=True)
|
137 |
+
if urban:
|
138 |
+
filters['zip_code']['urban'] = True
|
139 |
rural = st.checkbox('Rural', value=True)
|
140 |
+
if rural:
|
141 |
+
filters['zip_code']['rural'] = True
|
142 |
|
143 |
+
recency = st.slider(label='Месяцев с момента покупки', min_value=int(data_train.recency.min()), max_value=int(data_train.recency.max()), value=(int(data_train.recency.min()), int(data_train.recency.max())))
|
144 |
+
filters['recency'] = recency
|
145 |
|
146 |
+
disabled = False
|
147 |
if not first_group and not second_group and not third_group and not fourth_group and not fifth_group and not sixth_group and not seventh_group:
|
148 |
+
st.error('Необходимо выбрать хотя бы один класс')
|
149 |
+
disabled = True
|
150 |
+
elif not surburban and not urban and not rural:
|
151 |
+
st.error('Необходимо выбрать хотя бы один почтовый индекс')
|
152 |
+
disabled = True
|
153 |
+
|
154 |
+
filter_data = st.button(label='Отфильтровать', disabled=disabled)
|
155 |
+
|
156 |
+
if filter_data:
|
157 |
+
filtered_dataset = tools.filter_data(data_train, filters)
|
158 |
+
if filtered_dataset is None:
|
159 |
+
st.error('Нет подходящих под выбранный фильтр клиентов. Поп��обуйте изменить фильтр.')
|
160 |
+
sample_size = 7 if filtered_dataset.shape[0] >= 7 else filtered_dataset.shape[0]
|
161 |
+
example = filtered_dataset.sample(sample_size)
|
162 |
+
st.write('Пример пользователей, которым будет отправлена реклама')
|
163 |
+
st.dataframe(example)
|
164 |
+
st.info(f'Количество клиентов, которым реклама будет отправлена: _**{filtered_dataset.shape[0]}**_ ({filtered_dataset.shape[0] / data_train.shape[0] * 100 :.2f}% от всех клиентов)')
|
165 |
else:
|
166 |
+
# нельзя отправить рекламу, до фильтрации
|
167 |
+
disabled = True
|
168 |
+
|
169 |
+
send_promo = st.button('Отправить рекламу и посмотреть результат', disabled=disabled)
|
170 |
+
if send_promo:
|
171 |
+
pass
|
172 |
+
|