customeranalytics 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. customeranalytics/__init__.py +319 -0
  2. customeranalytics/configs.py +480 -0
  3. customeranalytics/data_storage_configurations/__init__.py +245 -0
  4. customeranalytics/data_storage_configurations/data_access.py +165 -0
  5. customeranalytics/data_storage_configurations/data_works_pipeline.py +207 -0
  6. customeranalytics/data_storage_configurations/es_create_index.py +544 -0
  7. customeranalytics/data_storage_configurations/logger.py +36 -0
  8. customeranalytics/data_storage_configurations/query_es.py +157 -0
  9. customeranalytics/data_storage_configurations/reports.py +684 -0
  10. customeranalytics/data_storage_configurations/schedule_data_integration.py +355 -0
  11. customeranalytics/docs/queries.yaml +245 -0
  12. customeranalytics/docs/web_configs.yaml +2 -0
  13. customeranalytics/exploratory_analysis/__init__.py +109 -0
  14. customeranalytics/exploratory_analysis/churn.py +232 -0
  15. customeranalytics/exploratory_analysis/cohorts.py +535 -0
  16. customeranalytics/exploratory_analysis/descriptive_statistics.py +470 -0
  17. customeranalytics/exploratory_analysis/funnels.py +407 -0
  18. customeranalytics/exploratory_analysis/product_analytics.py +340 -0
  19. customeranalytics/exploratory_analysis/promotion_analytics.py +299 -0
  20. customeranalytics/exploratory_analysis/rfm.py +202 -0
  21. customeranalytics/exploratory_analysis/sample_data/sample_data_avg_order_count_per_promo_per_cust.csv +99 -0
  22. customeranalytics/exploratory_analysis/sample_data/sample_data_chart_1_search.csv +2 -0
  23. customeranalytics/exploratory_analysis/sample_data/sample_data_chart_2_search.csv +46 -0
  24. customeranalytics/exploratory_analysis/sample_data/sample_data_chart_3_search.csv +2 -0
  25. customeranalytics/exploratory_analysis/sample_data/sample_data_chart_4_search.csv +2 -0
  26. customeranalytics/exploratory_analysis/sample_data/sample_data_churn.csv +2 -0
  27. customeranalytics/exploratory_analysis/sample_data/sample_data_churn_weekly.csv +10 -0
  28. customeranalytics/exploratory_analysis/sample_data/sample_data_client_feature_predicted.csv +1590 -0
  29. customeranalytics/exploratory_analysis/sample_data/sample_data_client_kpis.csv +7060 -0
  30. customeranalytics/exploratory_analysis/sample_data/sample_data_clvrfm_anomaly.csv +561 -0
  31. customeranalytics/exploratory_analysis/sample_data/sample_data_clvsegments_amount.csv +8 -0
  32. customeranalytics/exploratory_analysis/sample_data/sample_data_customer_journey.csv +18 -0
  33. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_clv.csv +103 -0
  34. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_cohort_downloads.csv +68 -0
  35. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_cohort_from_1_to_2.csv +66 -0
  36. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_cohort_from_2_to_3.csv +64 -0
  37. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_cohort_from_3_to_4.csv +55 -0
  38. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_dimension_values.csv +199 -0
  39. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_funnel.csv +36 -0
  40. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_funnel_downloads.csv +68 -0
  41. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_inorganic_ratio.csv +6070 -0
  42. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_orders.csv +57 -0
  43. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_organic_orders.csv +64 -0
  44. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_products_of_sales.csv +12574 -0
  45. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_promotion_discount.csv +64 -0
  46. customeranalytics/exploratory_analysis/sample_data/sample_data_daily_promotion_revenue.csv +6070 -0
  47. customeranalytics/exploratory_analysis/sample_data/sample_data_dcohort_anomaly.csv +6 -0
  48. customeranalytics/exploratory_analysis/sample_data/sample_data_dcohort_anomaly_2.csv +118 -0
  49. customeranalytics/exploratory_analysis/sample_data/sample_data_deliver.csv +47 -0
  50. customeranalytics/exploratory_analysis/sample_data/sample_data_deliver_kpis.csv +2 -0
  51. customeranalytics/exploratory_analysis/sample_data/sample_data_deliver_weekday_hour.csv +25 -0
  52. customeranalytics/exploratory_analysis/sample_data/sample_data_dfunnel_anomaly.csv +118 -0
  53. customeranalytics/exploratory_analysis/sample_data/sample_data_dimension_kpis.csv +199 -0
  54. customeranalytics/exploratory_analysis/sample_data/sample_data_dorders_anomaly.csv +85 -0
  55. customeranalytics/exploratory_analysis/sample_data/sample_data_frequency_clusters.csv +6 -0
  56. customeranalytics/exploratory_analysis/sample_data/sample_data_frequency_recency.csv +3740 -0
  57. customeranalytics/exploratory_analysis/sample_data/sample_data_hourly_funnel.csv +25 -0
  58. customeranalytics/exploratory_analysis/sample_data/sample_data_hourly_funnel_downloads.csv +25 -0
  59. customeranalytics/exploratory_analysis/sample_data/sample_data_hourly_inorganic_ratio.csv +2353 -0
  60. customeranalytics/exploratory_analysis/sample_data/sample_data_hourly_orders.csv +25 -0
  61. customeranalytics/exploratory_analysis/sample_data/sample_data_hourly_organic_orders.csv +2353 -0
  62. customeranalytics/exploratory_analysis/sample_data/sample_data_inorganic_orders_per_promotion_per_day.csv +6070 -0
  63. customeranalytics/exploratory_analysis/sample_data/sample_data_kpis.csv +2 -0
  64. customeranalytics/exploratory_analysis/sample_data/sample_data_monetary_clusters.csv +6 -0
  65. customeranalytics/exploratory_analysis/sample_data/sample_data_monetary_frequency.csv +3740 -0
  66. customeranalytics/exploratory_analysis/sample_data/sample_data_monthly_funnel.csv +5 -0
  67. customeranalytics/exploratory_analysis/sample_data/sample_data_monthly_funnel_downloads.csv +5 -0
  68. customeranalytics/exploratory_analysis/sample_data/sample_data_monthly_orders.csv +4 -0
  69. customeranalytics/exploratory_analysis/sample_data/sample_data_most_combined_products.csv +11 -0
  70. customeranalytics/exploratory_analysis/sample_data/sample_data_most_ordered_categories.csv +11 -0
  71. customeranalytics/exploratory_analysis/sample_data/sample_data_most_ordered_products.csv +11 -0
  72. customeranalytics/exploratory_analysis/sample_data/sample_data_order_and_payment_amount_differences.csv +91 -0
  73. customeranalytics/exploratory_analysis/sample_data/sample_data_prepare.csv +101 -0
  74. customeranalytics/exploratory_analysis/sample_data/sample_data_prepare_weekday_hour.csv +25 -0
  75. customeranalytics/exploratory_analysis/sample_data/sample_data_product_kpis.csv +201 -0
  76. customeranalytics/exploratory_analysis/sample_data/sample_data_product_usage_before_after_amount_accept.csv +11 -0
  77. customeranalytics/exploratory_analysis/sample_data/sample_data_product_usage_before_after_amount_reject.csv +7 -0
  78. customeranalytics/exploratory_analysis/sample_data/sample_data_product_usage_before_after_orders_accept.csv +11 -0
  79. customeranalytics/exploratory_analysis/sample_data/sample_data_product_usage_before_after_orders_reject.csv +11 -0
  80. customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_comparison.csv +91 -0
  81. customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_kpis.csv +99 -0
  82. customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_number_of_customer.csv +99 -0
  83. customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_usage_before_after_amount_accept.csv +81 -0
  84. customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_usage_before_after_amount_reject.csv +11 -0
  85. customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_usage_before_after_orders_accept.csv +55 -0
  86. customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_usage_before_after_orders_reject.csv +37 -0
  87. customeranalytics/exploratory_analysis/sample_data/sample_data_purchase_amount_distribution.csv +22 -0
  88. customeranalytics/exploratory_analysis/sample_data/sample_data_recency_clusters.csv +6 -0
  89. customeranalytics/exploratory_analysis/sample_data/sample_data_recency_monetary.csv +3740 -0
  90. customeranalytics/exploratory_analysis/sample_data/sample_data_rfm.csv +2001 -0
  91. customeranalytics/exploratory_analysis/sample_data/sample_data_ride.csv +101 -0
  92. customeranalytics/exploratory_analysis/sample_data/sample_data_ride_weekday_hour.csv +25 -0
  93. customeranalytics/exploratory_analysis/sample_data/sample_data_segmentation.csv +11 -0
  94. customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_daily_before_after_amount.csv +10 -0
  95. customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_daily_before_after_orders.csv +9 -0
  96. customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_monthly_before_after_amount.csv +10 -0
  97. customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_monthly_before_after_orders.csv +11 -0
  98. customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_weekly_before_after_amount.csv +10 -0
  99. customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_weekly_before_after_orders.csv +10 -0
  100. customeranalytics/exploratory_analysis/sample_data/sample_data_user_counts_per_order_seq.csv +47 -0
  101. customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_average_order_per_user.csv +13 -0
  102. customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_average_payment_amount.csv +13 -0
  103. customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_average_session_per_user.csv +13 -0
  104. customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_cohort_downloads.csv +13 -0
  105. customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_cohort_from_1_to_2.csv +13 -0
  106. customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_cohort_from_2_to_3.csv +13 -0
  107. customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_cohort_from_3_to_4.csv +12 -0
  108. customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_funnel.csv +25 -0
  109. customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_funnel_downloads.csv +25 -0
  110. customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_orders.csv +10 -0
  111. customeranalytics/ml_process/__init__.py +64 -0
  112. customeranalytics/ml_process/ab_test.py +615 -0
  113. customeranalytics/ml_process/anomaly_detection.py +456 -0
  114. customeranalytics/ml_process/clv_prediction.py +301 -0
  115. customeranalytics/ml_process/customer_segmentation.py +354 -0
  116. customeranalytics/ml_process/delivery_analytics.py +754 -0
  117. customeranalytics/utils.py +186 -0
  118. customeranalytics/web/app/__init__.py +37 -0
  119. customeranalytics/web/app/base/__init__.py +9 -0
  120. customeranalytics/web/app/base/forms.py +16 -0
  121. customeranalytics/web/app/base/models.py +43 -0
  122. customeranalytics/web/app/base/routes.py +126 -0
  123. customeranalytics/web/app/base/static/assets/css/app.css +20 -0
  124. customeranalytics/web/app/base/static/assets/fonts/Inter-Black.woff +0 -0
  125. customeranalytics/web/app/base/static/assets/fonts/Inter-Black.woff2 +0 -0
  126. customeranalytics/web/app/base/static/assets/fonts/Inter-BlackItalic.woff +0 -0
  127. customeranalytics/web/app/base/static/assets/fonts/Inter-BlackItalic.woff2 +0 -0
  128. customeranalytics/web/app/base/static/assets/fonts/Inter-Bold.woff +0 -0
  129. customeranalytics/web/app/base/static/assets/fonts/Inter-Bold.woff2 +0 -0
  130. customeranalytics/web/app/base/static/assets/fonts/Inter-BoldItalic.woff +0 -0
  131. customeranalytics/web/app/base/static/assets/fonts/Inter-BoldItalic.woff2 +0 -0
  132. customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraBold.woff +0 -0
  133. customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraBold.woff2 +0 -0
  134. customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraBoldItalic.woff +0 -0
  135. customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraBoldItalic.woff2 +0 -0
  136. customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraLight-BETA.woff +0 -0
  137. customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraLight-BETA.woff2 +0 -0
  138. customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraLightItalic-BETA.woff +0 -0
  139. customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraLightItalic-BETA.woff2 +0 -0
  140. customeranalytics/web/app/base/static/assets/fonts/Inter-Italic.woff +0 -0
  141. customeranalytics/web/app/base/static/assets/fonts/Inter-Italic.woff2 +0 -0
  142. customeranalytics/web/app/base/static/assets/fonts/Inter-Light-BETA.woff +0 -0
  143. customeranalytics/web/app/base/static/assets/fonts/Inter-Light-BETA.woff2 +0 -0
  144. customeranalytics/web/app/base/static/assets/fonts/Inter-LightItalic-BETA.woff +0 -0
  145. customeranalytics/web/app/base/static/assets/fonts/Inter-LightItalic-BETA.woff2 +0 -0
  146. customeranalytics/web/app/base/static/assets/fonts/Inter-Medium.woff +0 -0
  147. customeranalytics/web/app/base/static/assets/fonts/Inter-Medium.woff2 +0 -0
  148. customeranalytics/web/app/base/static/assets/fonts/Inter-MediumItalic.woff +0 -0
  149. customeranalytics/web/app/base/static/assets/fonts/Inter-MediumItalic.woff2 +0 -0
  150. customeranalytics/web/app/base/static/assets/fonts/Inter-Regular.woff +0 -0
  151. customeranalytics/web/app/base/static/assets/fonts/Inter-Regular.woff2 +0 -0
  152. customeranalytics/web/app/base/static/assets/fonts/Inter-SemiBold.woff +0 -0
  153. customeranalytics/web/app/base/static/assets/fonts/Inter-SemiBold.woff2 +0 -0
  154. customeranalytics/web/app/base/static/assets/fonts/Inter-SemiBoldItalic.woff +0 -0
  155. customeranalytics/web/app/base/static/assets/fonts/Inter-SemiBoldItalic.woff2 +0 -0
  156. customeranalytics/web/app/base/static/assets/fonts/Inter-Thin-BETA.woff +0 -0
  157. customeranalytics/web/app/base/static/assets/fonts/Inter-Thin-BETA.woff2 +0 -0
  158. customeranalytics/web/app/base/static/assets/fonts/Inter-ThinItalic-BETA.woff +0 -0
  159. customeranalytics/web/app/base/static/assets/fonts/Inter-ThinItalic-BETA.woff2 +0 -0
  160. customeranalytics/web/app/base/static/assets/fonts/Inter-italic.var.woff2 +0 -0
  161. customeranalytics/web/app/base/static/assets/fonts/Inter-upright.var.woff2 +0 -0
  162. customeranalytics/web/app/base/static/assets/fonts/Inter.var.woff2 +0 -0
  163. customeranalytics/web/app/base/static/assets/fonts/fa-brands-400.eot +0 -0
  164. customeranalytics/web/app/base/static/assets/fonts/fa-brands-400.svg +3570 -0
  165. customeranalytics/web/app/base/static/assets/fonts/fa-brands-400.ttf +0 -0
  166. customeranalytics/web/app/base/static/assets/fonts/fa-brands-400.woff +0 -0
  167. customeranalytics/web/app/base/static/assets/fonts/fa-brands-400.woff2 +0 -0
  168. customeranalytics/web/app/base/static/assets/fonts/fa-regular-400.eot +0 -0
  169. customeranalytics/web/app/base/static/assets/fonts/fa-regular-400.svg +801 -0
  170. customeranalytics/web/app/base/static/assets/fonts/fa-regular-400.ttf +0 -0
  171. customeranalytics/web/app/base/static/assets/fonts/fa-regular-400.woff +0 -0
  172. customeranalytics/web/app/base/static/assets/fonts/fa-regular-400.woff2 +0 -0
  173. customeranalytics/web/app/base/static/assets/fonts/fa-solid-900.eot +0 -0
  174. customeranalytics/web/app/base/static/assets/fonts/fa-solid-900.svg +5028 -0
  175. customeranalytics/web/app/base/static/assets/fonts/fa-solid-900.ttf +0 -0
  176. customeranalytics/web/app/base/static/assets/fonts/fa-solid-900.woff +0 -0
  177. customeranalytics/web/app/base/static/assets/fonts/fa-solid-900.woff2 +0 -0
  178. customeranalytics/web/app/base/static/assets/img/avatars/avatar-1.png +0 -0
  179. customeranalytics/web/app/base/static/assets/img/avatars/avatar-2.png +0 -0
  180. customeranalytics/web/app/base/static/assets/img/avatars/avatar-3.png +0 -0
  181. customeranalytics/web/app/base/static/assets/img/avatars/avatar-4.png +0 -0
  182. customeranalytics/web/app/base/static/assets/img/avatars/avatar-5.png +0 -0
  183. customeranalytics/web/app/base/static/assets/img/avatars/info.jpeg +0 -0
  184. customeranalytics/web/app/base/static/assets/img/icons/customanality.png +0 -0
  185. customeranalytics/web/app/base/static/assets/img/icons/icon-48x48.png +0 -0
  186. customeranalytics/web/app/base/static/assets/js/app.js +2 -0
  187. customeranalytics/web/app/base/static/assets/js/app.js.LICENSE +566 -0
  188. customeranalytics/web/app/base/static/assets/js/app.js.LICENSE.txt +556 -0
  189. customeranalytics/web/app/base/static/assets/js/modules/bootstrap.js +15 -0
  190. customeranalytics/web/app/base/static/assets/js/modules/chartjs.js +4 -0
  191. customeranalytics/web/app/base/static/assets/js/modules/feather.js +8 -0
  192. customeranalytics/web/app/base/static/assets/js/modules/flatpickr.js +4 -0
  193. customeranalytics/web/app/base/static/assets/js/modules/moment.js +4 -0
  194. customeranalytics/web/app/base/static/assets/js/modules/sidebar.js +23 -0
  195. customeranalytics/web/app/base/static/assets/js/modules/theme.js +16 -0
  196. customeranalytics/web/app/base/templates/accounts/login.html +81 -0
  197. customeranalytics/web/app/base/templates/accounts/register.html +80 -0
  198. customeranalytics/web/app/base/templates/includes/footer.html +28 -0
  199. customeranalytics/web/app/base/templates/includes/navigation.html +51 -0
  200. customeranalytics/web/app/base/templates/includes/scripts.html +3 -0
  201. customeranalytics/web/app/base/templates/includes/sidebar.html +146 -0
  202. customeranalytics/web/app/base/templates/layouts/base-fullscreen.html +36 -0
  203. customeranalytics/web/app/base/templates/layouts/base.html +84 -0
  204. customeranalytics/web/app/base/util.py +30 -0
  205. customeranalytics/web/app/home/__init__.py +9 -0
  206. customeranalytics/web/app/home/forms.py +1039 -0
  207. customeranalytics/web/app/home/models.py +438 -0
  208. customeranalytics/web/app/home/profiles.py +310 -0
  209. customeranalytics/web/app/home/routes.py +386 -0
  210. customeranalytics/web/app/home/search.py +288 -0
  211. customeranalytics/web/app/home/static/assets/img/avatars/avatar-1.png +0 -0
  212. customeranalytics/web/app/home/static/assets/img/avatars/avatar-2.png +0 -0
  213. customeranalytics/web/app/home/static/assets/img/avatars/avatar-3.png +0 -0
  214. customeranalytics/web/app/home/static/assets/img/avatars/avatar-4.png +0 -0
  215. customeranalytics/web/app/home/static/assets/img/avatars/avatar-5.png +0 -0
  216. customeranalytics/web/app/home/static/assets/img/icons/customanality.png +0 -0
  217. customeranalytics/web/app/home/static/assets/img/icons/icon-48x48.png +0 -0
  218. customeranalytics/web/app/home/templates/abtest-product.html +280 -0
  219. customeranalytics/web/app/home/templates/abtest-promotion.html +362 -0
  220. customeranalytics/web/app/home/templates/abtest-segments.html +298 -0
  221. customeranalytics/web/app/home/templates/add-data-delivery.html +184 -0
  222. customeranalytics/web/app/home/templates/add-data-product.html +177 -0
  223. customeranalytics/web/app/home/templates/add-data-purchase.html +392 -0
  224. customeranalytics/web/app/home/templates/anomaly.html +261 -0
  225. customeranalytics/web/app/home/templates/clv.html +211 -0
  226. customeranalytics/web/app/home/templates/cohorts.html +308 -0
  227. customeranalytics/web/app/home/templates/customer-segmentation.html +308 -0
  228. customeranalytics/web/app/home/templates/data-es.html +152 -0
  229. customeranalytics/web/app/home/templates/data-execute.html +397 -0
  230. customeranalytics/web/app/home/templates/delivery.html +404 -0
  231. customeranalytics/web/app/home/templates/funnel-customer.html +240 -0
  232. customeranalytics/web/app/home/templates/funnel-session.html +236 -0
  233. customeranalytics/web/app/home/templates/index.html +524 -0
  234. customeranalytics/web/app/home/templates/index2.html +515 -0
  235. customeranalytics/web/app/home/templates/page-403.html +46 -0
  236. customeranalytics/web/app/home/templates/page-404.html +45 -0
  237. customeranalytics/web/app/home/templates/page-500.html +45 -0
  238. customeranalytics/web/app/home/templates/pages-blank.html +33 -0
  239. customeranalytics/web/app/home/templates/pages-sign-in.html +67 -0
  240. customeranalytics/web/app/home/templates/pages-sign-up.html +61 -0
  241. customeranalytics/web/app/home/templates/product.html +203 -0
  242. customeranalytics/web/app/home/templates/profile.html +1009 -0
  243. customeranalytics/web/app/home/templates/rfm.html +254 -0
  244. customeranalytics/web/app/home/templates/search.html +167 -0
  245. customeranalytics/web/app/home/templates/settings.html +205 -0
  246. customeranalytics/web/app/home/templates/stats-desc.html +230 -0
  247. customeranalytics/web/app/home/templates/stats-purchase.html +221 -0
  248. customeranalytics/web/config.py +30 -0
  249. customeranalytics/web/db.sqlite3 +0 -0
  250. customeranalytics/web/run.py +30 -0
  251. customeranalytics-0.1.dist-info/METADATA +1102 -0
  252. customeranalytics-0.1.dist-info/RECORD +254 -0
  253. customeranalytics-0.1.dist-info/WHEEL +4 -0
  254. customeranalytics-0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,754 @@
1
+ from os.path import join, abspath
2
+ import pandas as pd
3
+ import numpy as np
4
+ from math import sqrt
5
+ import datetime
6
+ from scipy import stats
7
+ import pygeohash as gh
8
+ import random
9
+ import shutil
10
+ import time
11
+
12
+ import sys, os, inspect
13
+ currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
14
+ parentdir = os.path.dirname(currentdir)
15
+ sys.path.insert(0, parentdir)
16
+
17
+ from tensorflow.keras.models import Model
18
+ from tensorflow.keras.layers import Input
19
+ from tensorflow.keras.layers import Dense
20
+
21
+ from tensorflow.keras.optimizers import Adam
22
+
23
+ from kerastuner.tuners import RandomSearch
24
+ from kerastuner.engine.hyperparameters import HyperParameters
25
+
26
+ from customeranalytics.data_storage_configurations.query_es import QueryES
27
+ from customeranalytics.configs import not_required_default_values, default_es_port, default_es_host, \
28
+ delivery_anomaly_model_parameters, delivery_anomaly_model_hyper_parameters, delivery_threshold_z_score
29
+ from customeranalytics.utils import convert_to_date, get_index_group, dimension_decision, \
30
+ current_date_to_day, find_week_of_monday
31
+
32
+
33
+ class DeliveryAnalytics:
34
+ """
35
+ Delivery Analytics;
36
+
37
+ These analysis are only able to be run when delivery data source is created.
38
+ It detects abo-normal durations of rides according to customer location, location of order, hour of the order,
39
+ weekday of the order.
40
+
41
+ """
42
+ def __init__(self,
43
+ temporary_export_path,
44
+ has_delivery_connection=True,
45
+ host=None,
46
+ port=None,
47
+ download_index='downloads',
48
+ order_index='orders'):
49
+ """
50
+ ******* ******** *****
51
+ Dimensional :
52
+ Delivery Analytics must be created individually for dimensions.
53
+ For instance, the Data set contains locations dimension.
54
+ In this case, each location of 'orders' and 'downloads' indexes must be created individually.
55
+ by using 'download_index' and 'order_index' dimension can be assigned in order to create the Delivery Analytics.
56
+
57
+ download_index; downloads_location1 this will be the location dimension of
58
+ parameters in order to query downloads indexes; 'location1'.
59
+ download_index; orders_location1 this will be the location dimension of
60
+ parameters in order to query orders indexes; 'location1'.
61
+ ******* ******** *****
62
+ !!!
63
+
64
+ :param host: elasticsearch host
65
+ :param port: elasticsearch port
66
+ :param download_index: elasticsearch port
67
+ :param order_index: elasticsearch port
68
+ :param has_delivery_connection: is any additional delivery data source
69
+ """
70
+ self.port = default_es_port if port is None else port
71
+ self.host = default_es_host if host is None else host
72
+ self.download_index = download_index
73
+ self.order_index = order_index
74
+ self.has_delivery_connection = has_delivery_connection
75
+ self.temporary_export_path = temporary_export_path
76
+ self.query_es = QueryES(port=self.port, host=self.host)
77
+ self.data = pd.DataFrame()
78
+ self.has_data_end_date = False
79
+ self.has_location_data = False
80
+ self.features = lambda x: list(range(1, 8)) if x == 'weekday_hour' else list(range(1, 6))
81
+ self.features_norm = lambda x: [str(i) + '_norm' for i in self.features(x)]
82
+ self.model_data = {}
83
+ self.centroid = []
84
+ self.duration_metrics = ['deliver', 'prepare', 'ride', 'returns']
85
+ self.anomaly_metrics = ["customer", 'location', 'weekday_hour']
86
+ self._model = {}
87
+ self.params = delivery_anomaly_model_parameters
88
+ self.hyper_params = delivery_anomaly_model_hyper_parameters
89
+ self.hp = HyperParameters()
90
+ self.parameter_tuning_trials = 5 # number of parameter tuning trials
91
+ self.delivery_fields = ['id', 'client', 'session_start_date', 'date', 'payment_amount', 'discount_amount',
92
+ 'delivery.delivery_date', 'delivery.prepare_date',
93
+ 'delivery.return_date', 'delivery.latitude', 'delivery.longitude']
94
+ self.model = {m1: {m2: {"data": pd.DataFrame(),
95
+ "feature_data": pd.DataFrame(),
96
+ "train": [],
97
+ "test": [],
98
+ 'features': self.features(m1),
99
+ 'features_norm': self.features_norm(m1),
100
+ 'model': None,
101
+ 'params': self.params,
102
+ 'hyper_params': self.hyper_params}
103
+ for m2 in self.anomaly_metrics}
104
+ for m1 in self.duration_metrics}
105
+ self.functions = {m[0]: m[1] for m in
106
+ zip(self.anomaly_metrics, [self.customer_delivery_anomaly,
107
+ self.location_delivery_anomaly, self.weekday_hour_delivery_anomaly])}
108
+
109
+ def dimensional_query(self, boolean_query=None):
110
+ if dimension_decision(self.order_index):
111
+ if boolean_query is None:
112
+ boolean_query = [{"term": {"dimension": self.order_index}}]
113
+ else:
114
+ boolean_query += [{"term": {"dimension": self.order_index}}]
115
+ return boolean_query
116
+
117
+ def get_delivery_data(self):
118
+ """
119
+ collect data for delivery anomaly from orders index.
120
+ It needs only purchased orders.
121
+ Delivery object needs to be split.
122
+ """
123
+ self.query_es.query_builder(fields=self.delivery_fields,
124
+ boolean_queries=self.dimensional_query([{"term": {"actions.purchased": True}}]))
125
+
126
+ self.data = pd.DataFrame(self.query_es.get_data_from_es()).rename(
127
+ columns={'id': 'order_id', 'delivery.delivery_date': 'delivery_date',
128
+ 'delivery.prepare_date': 'prepare_date', 'delivery.return_date': 'return_date',
129
+ 'delivery.latitude': 'latitude', 'delivery.longitude': 'longitude'})
130
+
131
+ def data_source_date_decision(self, row, dt):
132
+ """
133
+ Check each columns 'date', 'prepare_date', 'return_date', 'latitude', 'longitude'
134
+ that it is assigned as default value of them
135
+
136
+ :param row: each row at data-frame
137
+ :param dt: columns names 'date', 'prepare_date', 'return_date', 'latitude', 'longitude'
138
+ """
139
+ if row[dt] == not_required_default_values.get(dt, None):
140
+ # get(dt, None )this is only for end_date (date) columns
141
+ return True
142
+ else:
143
+ return False
144
+
145
+ def get_has_data_end_date(self):
146
+ """
147
+ check data that has session end date.
148
+ Count unique end_date values and count total number of purchased sessions and calcualte the ratio
149
+ if the ratio is more than .1, end_date is assigned with sessions data source
150
+ """
151
+ end_date = list(self.data['date'])
152
+ unique_end_date = set(end_date)
153
+ if len(unique_end_date) / len(end_date) > 0.1:
154
+ self.has_data_end_date = True
155
+
156
+ def delivery_durations(self, row):
157
+ """
158
+ *** Duration Caşculations ***
159
+
160
+ prepare duration;
161
+ If prepare date is assigned it is possible to be calculated.
162
+ prepare = end_date - prepare_date
163
+ delivery duration;
164
+ If delivery data source is assigned delivery_date is required so, delivery durations can be calculated.
165
+ delivery = prepare_date - delivery_date
166
+ return duration;
167
+ If return date is assigned return duration can be calculated.
168
+ return = delivery_date - return_date
169
+
170
+ :param row: each row at data-frame
171
+ """
172
+ deliver, prepare, ride, returns = ['-'] * 4
173
+ if self.has_data_end_date: # if there is no significant end_date, assigned as session_start_date
174
+ session_end = row['date']
175
+ else:
176
+ session_end = row['session_start_date']
177
+
178
+ # if there is no prepare_date assigned it as end_date.
179
+ # It end_date is also not existed assigned as session_start_date
180
+ if row['prepare_date'] is not None:
181
+ prepare_date = row['prepare_date']
182
+ ride_start_date = row['prepare_date']
183
+ else:
184
+ if self.has_data_end_date:
185
+ prepare_date = row['date']
186
+ ride_start_date = row['prepare_date']
187
+ else:
188
+ prepare_date = None
189
+ ride_start_date = row['session_start_date']
190
+
191
+ # If there is no return_date, assign it as None
192
+ if row['return_date'] is not None:
193
+ return_date = row['return_date']
194
+ else:
195
+ return_date = None
196
+
197
+ # delivery_date is required column for delivery dta source
198
+ deliver = abs(row['delivery_date'] - session_end).total_seconds() / 60
199
+ # if both prepare_date and end_date is not assigned, both will be assigned as session_start_date.
200
+ # So, If they are not assigned prepare duration will be 0
201
+ if prepare_date == prepare_date:
202
+ prepare = abs(prepare_date - session_end).total_seconds() / 60
203
+ ride = abs(row['delivery_date'] - ride_start_date).total_seconds() / 60
204
+ # return duration will be assigned as '-' if duration_date isn`t existed
205
+ if return_date == return_date:
206
+ returns = abs(row['return_date'] - row['delivery_date']).total_seconds() / 60
207
+ return pd.Series([deliver, prepare, ride, returns])
208
+
209
+ def encode_geo_hash(self, row):
210
+ """
211
+ encoding locations (lat - lon) with geohash library
212
+ :params row: each row represents purchased transactions from orders index
213
+ """
214
+ try:
215
+ return gh.encode(row['latitude'], row['longitude'], precision=8)
216
+ except Exception as e:
217
+ return None
218
+
219
+ def parse_lat_lon(self, geohash):
220
+ """
221
+ parsing lat - lon from tuple format
222
+ """
223
+ return pd.Series([geohash[0], geohash[1]])
224
+
225
+ def geo_hash_perc7(self, _location, perc):
226
+ """
227
+ GeoHashing;
228
+ - Optimum precision for encoding lat - lon is decided as 7.
229
+ - decoding each transaction with geohash precision 7.
230
+ - apply parse_lat_lon (encoding hashed lat - lon).
231
+ - In that case, locations are centralized with hashed encoded - decoded lat - lon.
232
+ """
233
+ _location['location_perc%s' % str(perc)] = _location.apply(
234
+ lambda row: gh.decode(row['geohash_perc%s' % str(perc)]), axis=1)
235
+ _location[['lat%s' % str(perc), 'lon%s' % str(perc)]] = _location.apply(
236
+ lambda row: self.parse_lat_lon(row['location_perc%s' % str(perc)]), axis=1)
237
+ return _location
238
+
239
+ def convert_data_format(self, value, data_type):
240
+ """
241
+ if data_type is latitude or longitude, convert the value to float, otherwise, convert the value to timestamp.
242
+ """
243
+ if data_type in ['latitude', 'longitude']:
244
+ return float(value)
245
+ else:
246
+ return convert_to_date(value)
247
+
248
+ def data_manipulations(self):
249
+ """
250
+ 1. Converting date columns to timestamp.
251
+ 2. Converting the payment_amount and the discount_amount columns to float.
252
+ 3. apply lat - lon to geo-hash (perc=7)
253
+ 4. create hour - isoweekday - week columns. Week column is a timestamp that only covers Mondays of the week.
254
+ 5. Calculate Duration metrics;
255
+ a. Delivery Duration (required):
256
+ It is total duration between order purchased date and order of delivered date to the customer.
257
+ b. Prepare Duration (optional):
258
+ It is total duration between order purchased date and ordered prepared date.
259
+ c. Ride Duration (optional):
260
+ It is total duration between order purchased date and the date
261
+ which the delivery person returns to the first location after the order is delivered.
262
+
263
+ """
264
+ for dt in ['session_start_date', 'delivery_date']:
265
+ self.data[dt] = self.data.apply(lambda row: convert_to_date(row[dt]), axis=1)
266
+
267
+ for dt in ['date', 'prepare_date', 'return_date', 'latitude', 'longitude']:
268
+ self.data[dt] = self.data.apply(
269
+ lambda row: self.convert_data_format(row[dt], dt)
270
+ if not self.data_source_date_decision(row, dt) else None, axis=1)
271
+
272
+ for a in ['payment_amount', 'discount_amount']:
273
+ self.data[a] = self.data[a].apply(lambda x: float(x))
274
+
275
+ # geo hash encoding-decoding precision=7
276
+ self.data['geohash_perc7'] = self.data.apply(lambda row: self.encode_geo_hash(row), axis=1)
277
+
278
+ # weekday and hour
279
+ self.data['isoweekday'] = self.data['session_start_date'].apply(lambda x: x.isoweekday())
280
+ self.data['hour'] = self.data['session_start_date'].apply(lambda x: x.hour)
281
+ self.data['week'] = self.data['session_start_date'].apply(lambda x: find_week_of_monday(x))
282
+
283
+ # duration calculations; ride, delivery, prepare
284
+ self.data[self.duration_metrics] = self.data.apply(lambda row: self.delivery_durations(row), axis=1)
285
+
286
+ def min_max_norm(self, value, _min, _max):
287
+ """
288
+ Min - Max Normalization for the feature set of AutoEncoder.
289
+ """
290
+ if abs(_max - _min) != 0:
291
+ return (value - _min) / abs(_max - _min)
292
+ else:
293
+ return 0
294
+
295
+ def min_max_norm_reverse(self, norm_value, _min, _max):
296
+ """
297
+ Reverse back to actual value from the normalized values.
298
+ """
299
+ return (norm_value * abs(_max - _min)) + _min
300
+
301
+ def create_feature_set_and_train_test_split(self):
302
+ """
303
+ Creating feature set of a given model. Split data into the train and test sets.
304
+ Assigned split data to the self._main_data dictionary. After the model trained process is done,
305
+ it is assigned to the self.model dictionary.
306
+ """
307
+ self._model['features_norm'] = [str(i) + '_norm' for i in range(1, 6)]
308
+ self._model['feature_data'] = self._model['feature_data'].reset_index(drop=True).reset_index()
309
+ index = list(self._model['feature_data']['index'])
310
+ _train_size = int(len(index) * 0.8)
311
+ _test_size = len(index) - _train_size
312
+ _train_index = random.sample(index, _train_size)
313
+ _test_index = list(set(index) - set(_train_index))
314
+
315
+ self._model['train'] = self._model['feature_data'].query("index in @_train_index")[self._model['features_norm']].values
316
+ self._model['test'] = self._model['feature_data'].query("index in @_test_index")[self._model['features_norm']].values
317
+
318
+ def build_parameter_tuning_model(self, hp):
319
+ """
320
+ Parameter tuning model implementation. THis process can only be triggered with the keras-tuner object.
321
+ - Hidden Layer & Hidden Unit Decisions with an Example;
322
+ Hidden layer Count = 4
323
+ Hidden Layer Unit = 64
324
+ 1st Hidden Layer; hid. unit = 64
325
+ 2nd Hidden Layer; hid. unit = 64 / 2 = 32
326
+ 3rd Hidden Layer; hid. unit = 64 / (2*2) = 16
327
+ """
328
+ _input = Input(shape=(self._model['train'].shape[1],))
329
+ _unit = hp.Choice('h_l_unit', self._model['hyper_params']['h_l_unit'])
330
+ _layer = Dense(hp.Choice('h_l_unit', self._model['hyper_params']['h_l_unit']),
331
+ activation=hp.Choice('activation', self._model['hyper_params']['activation'])
332
+ )(_input)
333
+
334
+ # This process is decision of the hidden layer count
335
+ for i in range(1, hp.Choice('hidden_layer_count', self._model['hyper_params']['hidden_layer_count'])):
336
+ _unit = _unit / 2
337
+ _layer = Dense(_unit,
338
+ activation=hp.Choice('activation', self._model['hyper_params']['activation'])
339
+ )(_layer)
340
+
341
+ output = Dense(self._model['train'].shape[1], activation='sigmoid')(_layer)
342
+ model = Model(inputs=_input, outputs=output)
343
+ model.compile(loss=self._model['hyper_params']['loss'],
344
+ optimizer=Adam(lr=hp.Choice('lr', self._model['hyper_params']['lr'])))
345
+ return model
346
+
347
+ def remove_keras_tuner_folder(self):
348
+ """
349
+ removing keras tuner file. while you need to update the parameters it will affect rerun the parameter tuning.
350
+ It won`t start unless the folder has been removed.
351
+ """
352
+
353
+ try:
354
+ shutil.rmtree(join(self.temporary_export_path, "delivery_anomaly"))
355
+ except Exception as e:
356
+ print(" Parameter Tuning Keras Turner dummy files have already removed!!")
357
+
358
+ def parameter_tuning(self):
359
+ """
360
+ THis is the execution of the keras-tuner with RandomSearch
361
+ execution model function : self.build_parameter_tuning_model
362
+ hyper-parameters: check self.hyper_params
363
+ max_trials: check self.parameter_tuning_trials
364
+
365
+ After parameter tuning is finalized, the folder of the keras-tuner at the self.temporary_export_path is removed.
366
+ Parameter tuning is applied every time delivery analytics is triggered.
367
+ """
368
+ kwargs = {'directory': join(self.temporary_export_path, "delivery_anomaly")}
369
+ tuner = RandomSearch(self.build_parameter_tuning_model,
370
+ max_trials=self.parameter_tuning_trials,
371
+ hyperparameters=self.hp,
372
+ allow_new_entries=True,
373
+ objective='loss', **kwargs)
374
+ tuner.search(x=self._model['train'],
375
+ y=self._model['train'],
376
+ epochs=5,
377
+ batch_size=self._model['hyper_params']['batch_size'],
378
+ verbose=1,
379
+ validation_data=(self._model['test'], self._model['test']))
380
+
381
+ for p in tuner.get_best_hyperparameters()[0].values:
382
+ if p in list(self._model['params'].keys()):
383
+ self._model['params'][p] = tuner.get_best_hyperparameters()[0].values[p]
384
+ self.remove_keras_tuner_folder()
385
+
386
+ def calculating_loss_function(self, X, model, features):
387
+ """
388
+ Lost values from AutoEncoder
389
+ :param X: values list shape(feature count, number of sample size)
390
+ :param model: trained model
391
+ :param features: feature list
392
+ :return: anomaly scores
393
+ """
394
+ y_pred, Y = model.predict(X), X
395
+ anomaly_calculations = list(map(lambda x: np.mean([abs(x[0][f] - x[1][f]) for f in range(len(features))]),
396
+ zip(y_pred, Y)))
397
+ return anomaly_calculations
398
+
399
+ def norm_values_outlier(self, scores):
400
+ """
401
+ Scoring each AutoEncoder outputs with p-value of the Standart Normal Distribution
402
+ """
403
+ mean_scores = np.mean(scores)
404
+ std_scores = np.std(scores)
405
+ standart_error = delivery_threshold_z_score * sqrt(std_scores / len(scores))
406
+ return mean_scores + standart_error
407
+
408
+ def detect_outliers(self, type):
409
+ """
410
+ 1. Assign each AtutoEncoder score to Anomaly Scores.
411
+ 2. Calculate standart_error_right_tail from self.norm_values_outlier
412
+ 3. assign label as
413
+ a. if standart_error_right_tail > value, anomaly = 0
414
+ b. if standart_error_right_tail < value, anomaly = 1
415
+ """
416
+ self._model['feature_data']['anomaly_scores'] = self.calculating_loss_function(
417
+ self._model['feature_data'][self._model['features_norm']].values,
418
+ self._model['model'], self._model['features_norm'])
419
+ standart_error_right_tail = self.norm_values_outlier(list(self._model['feature_data']['anomaly_scores']))
420
+ self._model['feature_data'][type + '_anomaly'] = self._model['feature_data']['anomaly_scores'].apply(
421
+ lambda x: 1 if x > standart_error_right_tail else 0)
422
+
423
+ def build_model(self, type):
424
+ """
425
+ Creating Auto Encoder Network with tensorfow, keras
426
+ """
427
+ _input = Input(shape=(self._model['train'].shape[1],))
428
+ _unit = self._model['params']['h_l_unit']
429
+ _layer = Dense(self._model['params']['h_l_unit'],
430
+ activation=self._model['params']['activation']
431
+ )(_input)
432
+
433
+ for i in range(1, self._model['params']['hidden_layer_count']):
434
+ _unit = _unit / 2
435
+ _layer = Dense(_unit, activation=self._model['params']['activation'])(_layer)
436
+
437
+ output = Dense(self._model['train'].shape[1], activation='sigmoid')(_layer)
438
+ self._model['model'] = Model(inputs=_input, outputs=output)
439
+ self._model['model'].compile(loss='mse', optimizer=Adam(lr=self._model['params']['lr']), metrics=['mse'])
440
+ self._model['model'].fit(self._model['train'], self._model['train'],
441
+ epochs=int(self._model['params']['epochs']),
442
+ batch_size=int(self._model['params']['batch_size']),
443
+ verbose=True,
444
+ validation_split=0.2, shuffle=True)
445
+
446
+ self.detect_outliers(type)
447
+
448
+ def calculate_features_min_max_normalization(self):
449
+ """
450
+ Applying Min - Max Normalization to the feature set
451
+ """
452
+ max_value = self._model['feature_data'][self._model['features']].values.max()
453
+ min_value = self._model['feature_data'][self._model['features']].values.min()
454
+ for i in self._model['features']:
455
+ self._model['feature_data'][str(i) + '_norm'] = self._model['feature_data'][i].apply(
456
+ lambda x: self.min_max_norm(float(x), min_value, max_value))
457
+
458
+ def customer_delivery_anomaly(self, metric):
459
+ """
460
+ Customer Anomaly is related to durations per location of each customer per sequential order from 1 to 5.
461
+ Each customer's average duration is calculated. Each sequential value from 1 to 5 is used as feature data.
462
+
463
+ Example of feature set;
464
+
465
+ customer | 1st Order | 2nd Order | 3rd Order | 4th Order | 5th Order Anomaly
466
+ ___________________________________________________________________ _______
467
+ client_1 | 13.34 | 15.33 | 17.22 | 12.56 | 11.45 0
468
+ client_2 | 20.34 | 19.39 | 18.22 | 22.56 | 25.45 1
469
+ .... ..... ..... .... .... .... .... .... .... .... .... .... ....
470
+ .... ..... ..... .... .... .... .... .... .... .... .... .... ....
471
+ .... ..... ..... .... .... .... .... .... .... .... .... .... ....
472
+ client_22 | 13.34 | 10.39 | 12.23 | 12.55 | 11.93 0
473
+ client_24 | 13.59 | 11.94 | 16.40 | 12.60 | 60.12 **** 1
474
+
475
+
476
+ 1. calculate average durations (delivery - ride - prepare) per customer per order seq.
477
+ 2. remove client who has less than 5 orders. It might not represent the customer of correct durations and
478
+ the outlier ratio must be higher than expected.
479
+ 3. apply min-max normalization, parameter tuning, train model, and anomaly score calculation.
480
+
481
+ """
482
+ # create feature set with sequential values.
483
+ client_deliveries = self.data.groupby("client").agg(
484
+ {metric: "mean", "order_id": "count"}).reset_index().sort_values(by='order_id', ascending=False)
485
+ client_deliveries = client_deliveries.query("order_id > 5")
486
+ clients = list(client_deliveries['client'].unique())
487
+ self._model['data'] = self.data.query("client in @clients").sort_values(
488
+ ['client', 'session_start_date'], ascending=True)
489
+ self._model['data']['order_seq'] = self._model['data'].sort_values(by=['client', 'session_start_date']).groupby(
490
+ ['client']).cumcount() + 1
491
+
492
+ self._model['data'] = self._model['data'].merge(
493
+ self._model['data'].groupby('client').agg({"order_seq": "max"}).reset_index().rename(
494
+ columns={"order_seq": "order_seq_max"}),
495
+ on='client', how='left')
496
+ self._model['data']['order_seq'] = self._model['data']['order_seq_max'] - self._model['data']['order_seq']
497
+ self._model['data'] = self._model['data'].query("order_seq_max > 5 and order_seq < 5")
498
+ self._model['feature_data'] = pd.DataFrame(np.array(self._model['data'].pivot_table(columns='order_seq',
499
+ index='client',
500
+ aggfunc={metric: "mean"}
501
+ ).reset_index())).rename(
502
+ columns={0: "client"}).reset_index(drop=True).reset_index().fillna("-")
503
+
504
+ # remove non-numeric values from data set
505
+ for i in self._model['features']:
506
+ self._model['feature_data'] = self._model['feature_data'][self._model['feature_data'][i] != '-']
507
+
508
+ self.calculate_features_min_max_normalization() # normalization of the feature set
509
+ self.create_feature_set_and_train_test_split() # train - test split for train model
510
+ self.parameter_tuning() # parameter tuning
511
+ self.build_model('customer') # train model
512
+ self.model[metric]['customer'] = self._model # assigned model to self.model
513
+
514
+ def location_delivery_anomaly(self, metric):
515
+ """
516
+ Location-based anomaly is calculated with geo-hashed decoded locations with perc=7 and week of the transactions.
517
+ Basically, the feature set represents each location (geo-hash prec=7) of the last 4 weeks of average durations.
518
+
519
+ Example of feature set;
520
+
521
+ gehashed_prec_7 | last week | 2 week before | 3 week before | 4 week before Anomaly
522
+ ____________________________________________________________________________
523
+ 34.092349 - 43.092349 | 13.34 | 15.33 | 17.22 | 12.56 0
524
+ 34.023434 - 43.234342 | 20.34 | 19.39 | 18.22 | 22.56 1
525
+ .... ... .. ..... .... .... .... .... .... .. .. .... ...
526
+ .... ... .. ..... .... .... .... .... .... .. .. .... ...
527
+ .... ... .. ..... .... .... .... .... .... .. .. .... ...
528
+ 34.222332 - 43.023424 | 13.34 | 10.39 | 12.23 | 12.55 0
529
+ 34.932423 - 43.234444 | 13.59 | 11.94 | 16.40 | 60.60 **** 1
530
+
531
+ """
532
+ self._model['data'] = self.data.groupby(["week", 'geohash_perc7']).agg({metric: "mean"}).reset_index()
533
+ self._model['data']['week_seq'] = self._model['data'].sort_values(by=['geohash_perc7', 'week']).groupby(
534
+ ['geohash_perc7']).cumcount() + 1
535
+
536
+ locations_max_weeks = self._model['data'].groupby("geohash_perc7").agg(
537
+ {"week_seq": "max"}).reset_index().rename(columns={"week_seq": "week_seq_max"})
538
+ self._model['data'] = self._model['data'].merge(locations_max_weeks, on='geohash_perc7', how='left')
539
+ self._model['data']['week_seq'] = self._model['data']['week_seq_max'] - self._model['data']['week_seq']
540
+
541
+ self._model['feature_data'] = pd.DataFrame(np.array(self._model['data'].query("week_seq < 5").pivot_table(
542
+ columns="week_seq",
543
+ index='geohash_perc7',
544
+ aggfunc={metric: "max"}).reset_index())).rename(columns={0: "location"})
545
+
546
+ for f in self._model['features']:
547
+ mean_duration = self._model['feature_data'][self._model['feature_data'][f] == self._model['feature_data'][f]]
548
+ mean_duration = mean_duration[self._model['features']].values.mean()
549
+ self._model['feature_data'] = self._model['feature_data'].fillna(mean_duration)
550
+
551
+ self.calculate_features_min_max_normalization() # normalization of the feature set
552
+ self.create_feature_set_and_train_test_split() # train - test split for train model
553
+ self.parameter_tuning() # parameter tuning
554
+ self.build_model('location') # train model
555
+ self.model[metric]['location'] = self._model # assigned model to self.model
556
+
557
+ def weekday_hour_delivery_anomaly(self, metric):
558
+ """
559
+ Weekday - hour Anomaly Detection process;
560
+ 1. calculate average durations per hour per weekday
561
+ 2. assign Min-Max Normalization for the Average of Durations breakdown with weekday - hour
562
+ 3. assign abnormal if the normalized value more than 0.9.
563
+ """
564
+ self._model['feature_data'] = self.data.groupby(["isoweekday", "hour"]).agg(
565
+ {metric: "mean"}).reset_index().rename(columns={metric: "weekday_hour_norm"})
566
+ max_value = self._model['feature_data'][["weekday_hour_norm"]].values.max()
567
+ min_value = self._model['feature_data'][["weekday_hour_norm"]].values.min()
568
+ self._model['feature_data']['weekday_hour_norm'] = self._model['feature_data']['weekday_hour_norm'].apply(
569
+ lambda x: self.min_max_norm(float(x), min_value, max_value))
570
+
571
+ self._model['feature_data']['weekday_hour_anomaly'] = self._model['feature_data']['weekday_hour_norm'].apply(
572
+ lambda x: 1 if x > 0.9 else 0)
573
+
574
+ def merge_type_anomaly_data(self, data, metric):
575
+ """
576
+ Customer Anomaly Detection is applied for customer.
577
+ Location Anomaly Detection is applied for Location (geo-hashed prec=7).
578
+ Weekday Anomaly Detection is applied for weekday per hour.
579
+ This process is merged these detection into the each transaction. Then, decides for the abnormal transactions.
580
+ Rules of the Anomaly Decision;
581
+ - Rule 1; If 3 anomaly detection cases are positive, assign transaction as Abnormal Transaction.
582
+ - Rule 2; If location base or customer base are positive and weekday - hour case is positive,
583
+ assign as Abnormal Transaction.
584
+ """
585
+ # customers of detected abnormal transactions
586
+ data = data.merge(self.model[metric]['customer']['feature_data'][['client', 'customer_anomaly']],
587
+ on='client', how='left')
588
+
589
+ # locations (geo-hashed (perc=7)) of detected abnormal transactions
590
+ if self.has_location_data:
591
+ data = data.merge(
592
+ self.model[metric]['location']['feature_data'].rename(
593
+ columns={"location": "geohash_perc7"})[['geohash_perc7', 'location_anomaly']],
594
+ on='geohash_perc7', how='left')
595
+ else:
596
+ data['location_anomaly'] = 0
597
+
598
+ # weekday - hour of detected abnormal transactions
599
+ data = data.merge(self.model[metric]['weekday_hour']['feature_data']
600
+ [['hour', 'isoweekday', 'weekday_hour_anomaly']], on=['hour', 'isoweekday'], how='left')
601
+
602
+ data['anomaly_totals'] = data['customer_anomaly'] + data['location_anomaly'] + data['weekday_hour_anomaly']
603
+ data = data.query("anomaly_totals == 3 or ((customer_anomaly == 1 and weekday_hour_anomaly == 1) or (location_anomaly == 1 and weekday_hour_anomaly == 1))")
604
+ return data[['session_start_date', 'hour', 'isoweekday', 'latitude', 'longitude',
605
+ metric, 'client', 'customer_anomaly', 'location_anomaly', 'weekday_hour_anomaly']]
606
+
607
+ def decision_for_location_anomaly(self):
608
+ """
609
+ Has Delivery Data Source the latitude and longitude values?
610
+ """
611
+ if len(list(self.data['latitude'].unique())) > 1 or len(list(self.data['latitude'].unique())) > 1:
612
+ self.has_location_data = True
613
+ else:
614
+ self.anomaly_metrics = list(set(self.anomaly_metrics) - {'location'})
615
+
616
+ def delivery_kpis(self):
617
+ """
618
+ Delivery KPIs;
619
+
620
+ General metrics in order to follow up on how the delivery system is running at the business.
621
+ - Average Delivery Duration (min); Average delivery duration of all purchased transactions.
622
+ - Average Prepare Duration (min); Average prepare duration of all purchased transactions.
623
+ - Average Ride Duration (min); Average ride duration of all purchased transactions.
624
+ - Average Return Duration (min); Average return duration of all purchased transactions.
625
+ - Total Number Location (min); Total delivered locations of all purchased transactions.
626
+ """
627
+ kpis = {}
628
+ for m in self.duration_metrics:
629
+ kpis[m] = np.mean(self.data[m])
630
+
631
+ self.data['locations'] = self.data.apply(lambda row: "_".join([str(row['latitude']), str(row['longitude'])]), axis=1)
632
+ kpis['total_locations'] = len(np.unique(self.data['locations']))
633
+ return pd.DataFrame([kpis])
634
+
635
+ def execute_delivery_analysis(self):
636
+ """
637
+ 1. Check if there is delivery data source.
638
+ 2. collect the delivery data with deliver - return - prepare dates and lat - lon values.
639
+ 3. check for the end date which indicates session purchase date.
640
+ 4. assign data_manipulations. (check self.data_manipulations for details)
641
+ 5. Check if there is prepare - return date is assigned. Otherwise, Location Anomaly Detection is not triggered.
642
+ 6. Anomaly Detection for Duration Metrics (Deliver - Prepare - Ride):
643
+ Each metric (Deliver - Prepare - Ride) of Anomaly Detection is applied seperatelly.
644
+ For each metric, customer - location - weekday - hour base anomaly detection is calculated
645
+ and decide for transactions of abnormal values individually.
646
+ 7. Insert each metric of abnormal transactions to to reports index with anomaly_type.
647
+ 8. If delivery data source has latitude-longitude values, insert abnormal transactions with the lats - lons.
648
+ 9. Insert Normalized values of average durations per weekday per hour to the reports index.
649
+
650
+
651
+ """
652
+ if self.has_delivery_connection:
653
+ self.get_delivery_data()
654
+ self.get_has_data_end_date()
655
+ self.data_manipulations()
656
+ self.decision_for_location_anomaly()
657
+
658
+ # anomaly calculations
659
+ for metric in self.duration_metrics:
660
+ for type in self.anomaly_metrics:
661
+ print("type :", type, " || metric :", metric)
662
+ self._model = self.model[metric][type]
663
+ self.functions[type](metric)
664
+ _result = self.merge_type_anomaly_data(self.data, metric)
665
+ self.insert_into_reports_index(delivery_anomaly=_result, anomaly_type=metric, index=self.order_index)
666
+
667
+ # visualization and analytics calculation
668
+ for metric in self.duration_metrics:
669
+ self.insert_into_reports_index(delivery_anomaly=self.model[metric]['weekday_hour']['feature_data'],
670
+ anomaly_type=metric + '_weekday_hour', index=self.order_index)
671
+
672
+ if self.has_location_data:
673
+ for metric in self.duration_metrics:
674
+ _location = self.model[metric]['location']['data']
675
+ _location = self.geo_hash_perc7(_location, perc=7)
676
+ if sum(_location[metric]) != 0:
677
+ self.insert_into_reports_index(delivery_anomaly=_location,
678
+ anomaly_type=metric + '_location', index=self.order_index)
679
+ # delivery KPIs
680
+ self.insert_into_reports_index(delivery_anomaly=self.delivery_kpis(),
681
+ anomaly_type='deliver_kpis', index=self.order_index)
682
+
683
+ def insert_into_reports_index(self, delivery_anomaly, start_date=None, anomaly_type='ride', index='orders'):
684
+ """
685
+ via query_es.py, each report can be inserted into the reports index with the given format.
686
+ {"id": unique report id,
687
+ "report_date": start_date or current date,
688
+ "report_name": "delivery_anomaly",
689
+ "index": "main",
690
+ "report_types": {
691
+ "type": anomaly_type; 'deliver', 'prepare', 'ride', 'returns'
692
+ },
693
+ "data": delivery_anomaly
694
+ }
695
+ :param delivery_anomaly: data set, data frame
696
+ :param start_date: data start date
697
+ :param anomaly_type: data types; 'deliver', 'prepare', 'ride', 'returns'
698
+ :param index: dimentionality of data index orders_location1 ; dimension = location1
699
+ """
700
+ list_of_obj = [{"id": np.random.randint(200000000),
701
+ "report_date": current_date_to_day().isoformat() if start_date is None else start_date,
702
+ "report_name": "delivery_anomaly",
703
+ "index": get_index_group(index),
704
+ "report_types": {"type": anomaly_type},
705
+ "data": delivery_anomaly.fillna(0).to_dict("records")}]
706
+ self.query_es.insert_data_to_index(list_of_obj, index='reports')
707
+
708
+ def fetch(self, anomaly_type, start_date=None):
709
+ """
710
+ This allows us to query the created delivery_anomaly reports.
711
+ anomaly_type is crucial for us to collect the correct filters.
712
+ Example of queries;
713
+ - anomaly_type: delivery_anomaly,
714
+ - start_date: 2021-01-01T00:00:00
715
+
716
+ {'size': 10000000,
717
+ 'from': 0,
718
+ '_source': True,
719
+ 'query': {'bool': {'must': [
720
+ {'term': {'report_name': 'delivery_anomaly'}},
721
+ {"term": {"index": "orders_location1"}}
722
+ {'term': {'report_types.type': 'ride'}},
723
+ {'range': {'report_date': {'lt': '2021-04-01T00:00:00'}}}]}}}
724
+
725
+ - start date will be filtered from data frame. In this example; .query("daily > @start_date")
726
+
727
+ :param anomaly_type: 'deliver', 'prepare', 'ride', 'returns'
728
+ :param start_date: delivery anomaly executed date
729
+ :param index: index_name in order to get dimension_of data. If there is no dimension, no need to be assigned
730
+ :return: data frame
731
+ """
732
+ boolean_queries, date_queries = [], []
733
+ boolean_queries = [{"term": {"report_name": 'delivery_anomaly'}},
734
+ {"term": {"index": get_index_group(self.order_index)}},
735
+ {"term": {"report_types.type": anomaly_type}},
736
+ {'range': {'report_date': {
737
+ 'lt': current_date_to_day().isoformat() if start_date is None else start_date}}}]
738
+
739
+ self.query_es = QueryES(port=self.port,
740
+ host=self.host)
741
+ self.query_es.query_builder(fields=None, _source=True,
742
+ date_queries=date_queries,
743
+ boolean_queries=boolean_queries)
744
+ _res = self.query_es.get_data_from_es(index="reports")
745
+ return pd.DataFrame(_res[0]['_source']['data'])
746
+
747
+
748
+
749
+
750
+
751
+
752
+
753
+
754
+