{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport plotly.express as px\nfrom tqdm.notebook import tqdm","metadata":{"_uuid":"ebfbaa7f-af60-4901-812e-d31afaf67f9d","_cell_guid":"2ce0f8f3-18b3-42c4-acdf-f83278123a3b","execution":{"iopub.status.busy":"2022-03-27T20:02:53.360074Z","iopub.execute_input":"2022-03-27T20:02:53.361401Z","iopub.status.idle":"2022-03-27T20:02:53.366242Z","shell.execute_reply.started":"2022-03-27T20:02:53.36134Z","shell.execute_reply":"2022-03-27T20:02:53.365404Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Overview\n\nThis notebook shows a basic prediction using last week's best sellers and includes calculation of MAP@12 and a simple four fold validation.","metadata":{}},{"cell_type":"markdown","source":"# Data Loading","metadata":{}},{"cell_type":"markdown","source":"Uses some preprocessed data with minor feature engineering applied. See the data set [H&M Fashion Parquet Performance](https://www.kaggle.com/datasets/tbierhance/hm-fashion-recommendation-parquet).","metadata":{}},{"cell_type":"code","source":"customers = pd.read_parquet('../input/hm-fashion-recommendation-parquet/customers.parquet')\narticles = pd.read_parquet('../input/hm-fashion-recommendation-parquet/articles.parquet')\nsales = pd.read_parquet('../input/hm-fashion-recommendation-parquet/sales.parquet')\ncustomer_ids = pd.read_parquet('../input/hm-fashion-recommendation-parquet/customer_ids.parquet')\nsample_submission = pd.read_parquet('../input/hm-fashion-recommendation-parquet/sample_submission.parquet')","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:02:53.368011Z","iopub.execute_input":"2022-03-27T20:02:53.36894Z","iopub.status.idle":"2022-03-27T20:03:07.781496Z","shell.execute_reply.started":"2022-03-27T20:02:53.368869Z","shell.execute_reply":"2022-03-27T20:03:07.777401Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sales.head()","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:03:07.789581Z","iopub.execute_input":"2022-03-27T20:03:07.794241Z","iopub.status.idle":"2022-03-27T20:03:07.85128Z","shell.execute_reply.started":"2022-03-27T20:03:07.793813Z","shell.execute_reply":"2022-03-27T20:03:07.850429Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Metric: Mean Average Precision @ k\n\n\n","metadata":{}},{"cell_type":"markdown","source":"The metric MAP@k as defined for this competition can be calculated from the average precision @ k for every customer. Here is a simple implementation of the metric. It does NOT handle duplicates:","metadata":{"execution":{"iopub.status.busy":"2022-03-27T17:07:41.390961Z","iopub.execute_input":"2022-03-27T17:07:41.391271Z","iopub.status.idle":"2022-03-27T17:07:41.462805Z","shell.execute_reply.started":"2022-03-27T17:07:41.391239Z","shell.execute_reply":"2022-03-27T17:07:41.461704Z"}}},{"cell_type":"code","source":"def average_precision_score(y_true, y_score, k=None):\n    if k is None: k=len(y_score)\n    relevant = np.isin(y_score[:k], y_true) # relevant[i]==1 if y_score[i] is correct\n    patk = np.cumsum(relevant)/np.arange(1, len(y_score[:k])+1) # patk[0]==P@1, patk[1]==P@2, ...\n    return(np.sum(patk*relevant)/min(len(y_true), k)) # as defined by the competition","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:08:51.528468Z","iopub.execute_input":"2022-03-27T20:08:51.529567Z","iopub.status.idle":"2022-03-27T20:08:51.535711Z","shell.execute_reply.started":"2022-03-27T20:08:51.529511Z","shell.execute_reply":"2022-03-27T20:08:51.534742Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Some examples (using 4 predictions instead of 12 for the sake of clarity):","metadata":{}},{"cell_type":"code","source":"# Example 1: all predictions are wrong\ny_true = [1, 2, 3, 4]\ny_score = [5, 6, 7, 8]\naverage_precision_score(y_true, y_score)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:08:53.361503Z","iopub.execute_input":"2022-03-27T20:08:53.361844Z","iopub.status.idle":"2022-03-27T20:08:53.370694Z","shell.execute_reply.started":"2022-03-27T20:08:53.361795Z","shell.execute_reply":"2022-03-27T20:08:53.369683Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Example 2: all predictions are correct (prediction order does NOT matter)\ny_true = [1, 2, 3, 4]\ny_score = [4, 3, 2, 1]\naverage_precision_score(y_true, y_score)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:08:57.93579Z","iopub.execute_input":"2022-03-27T20:08:57.936588Z","iopub.status.idle":"2022-03-27T20:08:57.944848Z","shell.execute_reply.started":"2022-03-27T20:08:57.936535Z","shell.execute_reply":"2022-03-27T20:08:57.943736Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Example 3: first prediction is incorrect (prediction order DOES matter, ground truth order DOES NOT matter)\ny_true = [1, 2, 3, 4]\ny_score = [0, 2, 3, 4]\naverage_precision_score(y_true, y_score)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:08:58.870801Z","iopub.execute_input":"2022-03-27T20:08:58.871121Z","iopub.status.idle":"2022-03-27T20:08:58.879026Z","shell.execute_reply.started":"2022-03-27T20:08:58.871089Z","shell.execute_reply":"2022-03-27T20:08:58.877932Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Example 4: last prediction is incorrect (prediction order DOES matter, ground truth order DOES NOT matter)\ny_true = [1, 2, 3, 4]\ny_score = [1, 2, 3, 5]\naverage_precision_score(y_true, y_score)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:09:01.035916Z","iopub.execute_input":"2022-03-27T20:09:01.036214Z","iopub.status.idle":"2022-03-27T20:09:01.043935Z","shell.execute_reply.started":"2022-03-27T20:09:01.036184Z","shell.execute_reply":"2022-03-27T20:09:01.042921Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Example 5: y_true can be shorter than k\ny_true = [3, 4]\ny_score = [1, 2, 3, 4]\naverage_precision_score(y_true, y_score)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:09:01.873356Z","iopub.execute_input":"2022-03-27T20:09:01.873684Z","iopub.status.idle":"2022-03-27T20:09:01.880728Z","shell.execute_reply.started":"2022-03-27T20:09:01.873648Z","shell.execute_reply":"2022-03-27T20:09:01.879902Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Example 6: the ground truth y_true can be longer than k, however y_score should be truncated to the first k entries\ny_true = [1, 2, 3, 4, 5, 6]\ny_score = [1, 2, 6, 7, 5, 3]\nprint(f'AP@6: {average_precision_score(y_true, y_score, k=6):.4f}')\nprint(f'AP@4: {average_precision_score(y_true, y_score, k=4):.4f}') # predictions 5 and 3 are being ignored","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:09:04.403927Z","iopub.execute_input":"2022-03-27T20:09:04.404259Z","iopub.status.idle":"2022-03-27T20:09:04.411279Z","shell.execute_reply.started":"2022-03-27T20:09:04.404227Z","shell.execute_reply":"2022-03-27T20:09:04.410204Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train & Validate: use best sellers only\n\nThis methods derives the best selling articles for the week before the validation week. All customers that bought some article in the validation week will be scored. Returns the MAP@12 over all customers.","metadata":{}},{"cell_type":"code","source":"def train_validate(validation_week):\n    # get the best selling articles in the week before the prediction\n    train = sales[sales.week == validation_week-1]\n    best_sellers = train.groupby('article_id').size().nlargest(12).index.values\n    \n    # only include customers that bought something in the week to predict\n    validate = sales[sales.week==validation_week].groupby('customer_id').article_id.unique().reset_index(name='y_true')\n    # use best selling articles for the prediction for every customer\n    validate['y_score'] = validate.apply(lambda x: best_sellers, axis=1)\n    # calculate AP@12 for every customer\n    validate['ap@12'] = validate.apply(lambda row: average_precision_score(row['y_true'], row['y_score']), axis=1)\n    # return MAP@12 over all customers\n    return(validate['ap@12'].mean())","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:03:08.08512Z","iopub.execute_input":"2022-03-27T20:03:08.086075Z","iopub.status.idle":"2022-03-27T20:03:08.121004Z","shell.execute_reply.started":"2022-03-27T20:03:08.085952Z","shell.execute_reply":"2022-03-27T20:03:08.114509Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Four fold validation using the week 104 just before the test week 105, weeks 52 and 53 that are similar to the test week (one year ago) and week 78 which is in between.","metadata":{}},{"cell_type":"code","source":"validation_weeks = [52, 53, 78, 104]\nresults=[]\nfor (idx, validation_week) in enumerate(tqdm(validation_weeks)):\n    result = train_validate(validation_week)\n    print(f'Fold {idx} predicting week {validation_week}: MAP@12={result:.4f}')\n    results.append(result)\nprint()\nprint(f'Over all folds: MAP@12={np.mean(results):.4f}')","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:03:08.125302Z","iopub.execute_input":"2022-03-27T20:03:08.125738Z","iopub.status.idle":"2022-03-27T20:03:40.140762Z","shell.execute_reply.started":"2022-03-27T20:03:08.125683Z","shell.execute_reply":"2022-03-27T20:03:40.139774Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Final train","metadata":{}},{"cell_type":"markdown","source":"Derive best sellers for the last week.","metadata":{}},{"cell_type":"code","source":"TEST_WEEK = 105\ntrain = sales[sales.week == TEST_WEEK-1]\nbest_sellers = train.groupby('article_id').size().nlargest(12).index.values","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:03:40.142342Z","iopub.execute_input":"2022-03-27T20:03:40.142866Z","iopub.status.idle":"2022-03-27T20:03:40.233992Z","shell.execute_reply.started":"2022-03-27T20:03:40.142795Z","shell.execute_reply":"2022-03-27T20:03:40.233018Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Predict best sellers for every customer.","metadata":{}},{"cell_type":"code","source":"submission = customers[['customer_id']].copy()\nsubmission['y_score'] = submission.apply(lambda x: best_sellers, axis=1)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:03:40.235586Z","iopub.execute_input":"2022-03-27T20:03:40.235845Z","iopub.status.idle":"2022-03-27T20:03:47.12498Z","shell.execute_reply.started":"2022-03-27T20:03:40.235798Z","shell.execute_reply":"2022-03-27T20:03:47.12399Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Remap the custom integer customer_ids to the original customer_ids and format the list of articles.","metadata":{}},{"cell_type":"code","source":"submission = submission.merge(customer_ids)[['customer_id_original', 'y_score']].rename(columns={'customer_id_original': 'customer_id', 'y_score': 'prediction'})\nsubmission['prediction'] = submission.prediction.apply(lambda x: ' '.join([f'{e:010d}' for e in x]))\nsubmission.head()","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:03:47.126718Z","iopub.execute_input":"2022-03-27T20:03:47.127159Z","iopub.status.idle":"2022-03-27T20:03:58.921884Z","shell.execute_reply.started":"2022-03-27T20:03:47.127111Z","shell.execute_reply":"2022-03-27T20:03:58.92106Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T20:03:58.923262Z","iopub.execute_input":"2022-03-27T20:03:58.923994Z","iopub.status.idle":"2022-03-27T20:04:05.991792Z","shell.execute_reply.started":"2022-03-27T20:03:58.923949Z","shell.execute_reply":"2022-03-27T20:04:05.990821Z"},"trusted":true},"execution_count":null,"outputs":[]}]}