{"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":"markdown","source":"<h1 style=\"text-align: center;\"> Student Performance from Game Play - Exploratory Data Analysis</h1>\n<h2><center> <img src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Video_Game_Book.svg/480px-Video_Game_Book.svg.png\" alt=\"EducationGame img\"></center></h2>","metadata":{}},{"cell_type":"markdown","source":"## Table of Contents\n* [1. Data basic overview](#data-basic-overview)\n    * [Train and test data](#data-basic-overview-train-test)\n    * [Labels data](#data-basic-overview-labels)\n* [2. Exploring data](#exploring-data)\n    * [2.1 session_id](#session-id)\n    * [2.2 index](#index)\n    * [2.3 elapsed_time](#elapsed-time)\n    * [2.4 event properties: name, type, and id](#event-properties)\n    * [2.5. game room](#game-room)\n    * [2.6 level and level_group](#level)\n    * [2.7 click geo-location](#click-geo-location)\n    * [2.8 text](#text)\n    * [2.9 page](#page)\n    * [2.10 hover](#hover)\n    * [2.11 game properties: fullscreen, hq, and music](#game-properties)","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nimport seaborn as sns\nfrom wordcloud import WordCloud\nimport warnings\nimport gc\n\n# settings\nsns.set(style=\"whitegrid\", color_codes=True)\nwarnings.filterwarnings(\"ignore\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-03-27T16:15:23.158482Z","iopub.execute_input":"2023-03-27T16:15:23.159322Z","iopub.status.idle":"2023-03-27T16:15:24.368984Z","shell.execute_reply.started":"2023-03-27T16:15:23.159218Z","shell.execute_reply":"2023-03-27T16:15:24.367986Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 1. Data basic overview <a class=\"anchor\" id=\"data-basic-overview\"></a>\n\nThe goal of the competition is to predict the performance of game-based learning from game logs. The data include: \n- training set with game sessions data (`train.csv`);\n- correct answers for all questions for each session (`train_labels.csv`);\n- test set with game sessions data (`test.csv`);\n- sample submission file (`sample_submission.csv`).\n","metadata":{}},{"cell_type":"code","source":"dtypes = {'session_id': 'category',\n          'elapsed_time': np.int32,\n          'event_name': 'category',\n          'name': 'category',\n          'level': np.uint8,\n          'page': 'category',\n          'room_coor_x': np.float32,\n          'room_coor_y': np.float32,\n          'screen_coor_x': np.float32,\n          'screen_coor_y': np.float32,\n          'hover_duration': np.float32,\n          'text': 'category',\n          'fqid': 'category',\n          'room_fqid': 'category',\n          'text_fqid': 'category',\n          'fullscreen': np.int8,\n          'hq': np.int8,\n          'music': np.int8,\n          'level_group': 'category'}\n\ntrain = pd.read_csv('/kaggle/input/predict-student-performance-from-game-play/train.csv', dtype=dtypes)\ntest = pd.read_csv('/kaggle/input/predict-student-performance-from-game-play/test.csv', dtype=dtypes)\nlabels = pd.read_csv('/kaggle/input/predict-student-performance-from-game-play/train_labels.csv')\n\nprint('Train data shape:', train.shape)\nprint('Test data shape:', test.shape)\nprint('Labels data shape:', labels.shape)","metadata":{"execution":{"iopub.status.busy":"2023-03-27T16:15:24.372675Z","iopub.execute_input":"2023-03-27T16:15:24.373600Z","iopub.status.idle":"2023-03-27T16:17:44.170891Z","shell.execute_reply.started":"2023-03-27T16:15:24.373550Z","shell.execute_reply":"2023-03-27T16:17:44.169898Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Train and test data <a class=\"anchor\" id=\"data-basic-overview-train-test\"></a>","metadata":{}},{"cell_type":"code","source":"print(\"Sample of train data:\")\ntrain.head()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:17:44.172294Z","iopub.execute_input":"2023-03-27T16:17:44.172840Z","iopub.status.idle":"2023-03-27T16:17:44.206893Z","shell.execute_reply.started":"2023-03-27T16:17:44.172806Z","shell.execute_reply":"2023-03-27T16:17:44.206005Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Sample of test data:\")\ntest.head()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:17:44.209544Z","iopub.execute_input":"2023-03-27T16:17:44.210228Z","iopub.status.idle":"2023-03-27T16:17:44.360761Z","shell.execute_reply.started":"2023-03-27T16:17:44.210188Z","shell.execute_reply":"2023-03-27T16:17:44.359173Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Train data statistics:\")\ntrain.describe().apply(lambda x: x.apply('{0:.1f}'.format))","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:17:44.362211Z","iopub.execute_input":"2023-03-27T16:17:44.362708Z","iopub.status.idle":"2023-03-27T16:17:54.396023Z","shell.execute_reply.started":"2023-03-27T16:17:44.362674Z","shell.execute_reply":"2023-03-27T16:17:54.394451Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Test data statistics:\")\ntest.describe().apply(lambda x: x.apply('{0:.1f}'.format))","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:17:54.397950Z","iopub.execute_input":"2023-03-27T16:17:54.398467Z","iopub.status.idle":"2023-03-27T16:17:54.464409Z","shell.execute_reply.started":"2023-03-27T16:17:54.398429Z","shell.execute_reply":"2023-03-27T16:17:54.463040Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"stat = pd.DataFrame([train.nunique(), test.nunique()]).T.fillna(0)\nstat.columns = ['Number of unique values in train', 'Number of unique values in test']\nstat.head(30).style.format(\"{:,.0f}\").background_gradient(cmap='YlGn')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:17:54.466627Z","iopub.execute_input":"2023-03-27T16:17:54.467000Z","iopub.status.idle":"2023-03-27T16:18:05.977140Z","shell.execute_reply.started":"2023-03-27T16:17:54.466968Z","shell.execute_reply":"2023-03-27T16:18:05.976046Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_missing = train.isna().sum() / len(train) *100\nplt.figure(figsize=(20, 10))\npal = sns.color_palette(\"flare\", len(train_missing))\nrank = train_missing.argsort().argsort()\ng = sns.barplot(x=train_missing.index, y=train_missing, palette=np.array(pal[::])[rank])\ng.axes.set_title(\"Share of missing values in train data\", fontsize=18)\ng.bar_label(g.containers[0], fmt=\"%.1f%%\")\ng.xaxis.set_tick_params(rotation=35)\ndel train_missing, g, pal, rank","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:05.978694Z","iopub.execute_input":"2023-03-27T16:18:05.979603Z","iopub.status.idle":"2023-03-27T16:18:07.416662Z","shell.execute_reply.started":"2023-03-27T16:18:05.979563Z","shell.execute_reply":"2023-03-27T16:18:07.415404Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_missing = test.isna().sum() / len(test) *100\nplt.figure(figsize=(20, 10))\npal = sns.color_palette(\"flare\", len(test_missing))\nrank = test_missing.argsort().argsort()\ng = sns.barplot(x=test_missing.index, y=test_missing, palette=np.array(pal[::])[rank])\ng.axes.set_title(\"Share of missing values in test data\", fontsize=18)\ng.bar_label(g.containers[0], fmt=\"%.1f%%\")\ng.xaxis.set_tick_params(rotation=35)\ndel test_missing, g, pal, rank","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:07.417709Z","iopub.execute_input":"2023-03-27T16:18:07.418045Z","iopub.status.idle":"2023-03-27T16:18:08.005361Z","shell.execute_reply.started":"2023-03-27T16:18:07.418015Z","shell.execute_reply":"2023-03-27T16:18:08.003793Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Some columns are missing in all data (`session_level` in test). So its safe to drop them from the dataset:","metadata":{}},{"cell_type":"code","source":"test.drop(columns=['session_level'], inplace=True)\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-03-27T16:18:08.010441Z","iopub.execute_input":"2023-03-27T16:18:08.010820Z","iopub.status.idle":"2023-03-27T16:18:08.142628Z","shell.execute_reply.started":"2023-03-27T16:18:08.010789Z","shell.execute_reply":"2023-03-27T16:18:08.141639Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(20, 10))\ng = sns.heatmap(train.corr(), annot=True, square=True, cmap='coolwarm', annot_kws={'size': 15},fmt='.2f')\ng.tick_params(axis='x', labelsize=15)\ng.tick_params(axis='y', labelsize=15)\ng.set_title('Correlations in train data', size=20, pad=15)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:08.143874Z","iopub.execute_input":"2023-03-27T16:18:08.144434Z","iopub.status.idle":"2023-03-27T16:18:19.358839Z","shell.execute_reply.started":"2023-03-27T16:18:08.144399Z","shell.execute_reply":"2023-03-27T16:18:19.357897Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gc.collect()\nplt.figure(figsize=(20, 10))\ng = sns.heatmap(test.corr(), annot=True, square=True, cmap='coolwarm', annot_kws={'size': 15},fmt='.2f')\ng.tick_params(axis='x', labelsize=15)\ng.tick_params(axis='y', labelsize=15)\ng.set_title('Correlations in test data', size=20, pad=15)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:19.360559Z","iopub.execute_input":"2023-03-27T16:18:19.361517Z","iopub.status.idle":"2023-03-27T16:18:20.169355Z","shell.execute_reply.started":"2023-03-27T16:18:19.361462Z","shell.execute_reply":"2023-03-27T16:18:20.168406Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Labels data <a class=\"anchor\" id=\"data-basic-overview-labels\"></a>\n\n`train_labels.csv` includes two values:\n- `session_id`: does not equal to session_id from the training set, this is combination `<session_id>_<question #>`;\n- `correct`: flag for correct (1) or incorrect (0) answer.\n\nNote that during gameplay the player must eventually select the correct answer to continue. A \"correct answer\" here indicates that the player got the answer correct on their first attempt. For the list of quizzes see [Game Walkthrough](https://www.kaggle.com/competitions/predict-student-performance-from-game-play/discussion/384796) by [@pjmathematician](https://www.kaggle.com/pjmathematician). ","metadata":{}},{"cell_type":"code","source":"print(\"Sample of labels data:\")\nlabels.head()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:20.170768Z","iopub.execute_input":"2023-03-27T16:18:20.171302Z","iopub.status.idle":"2023-03-27T16:18:20.180842Z","shell.execute_reply.started":"2023-03-27T16:18:20.171266Z","shell.execute_reply":"2023-03-27T16:18:20.180008Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels.describe().T","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:20.182222Z","iopub.execute_input":"2023-03-27T16:18:20.182749Z","iopub.status.idle":"2023-03-27T16:18:20.216222Z","shell.execute_reply.started":"2023-03-27T16:18:20.182717Z","shell.execute_reply":"2023-03-27T16:18:20.215282Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"About 70% of the answers are correct, so the dataset is slightly imbalanced. Correctness varies across questions:","metadata":{}},{"cell_type":"code","source":"labels[['session_id_id', 'question_number']] = labels['session_id'].str.split('_', 1, expand=True)\nlabels['session_id_id'] = labels['session_id_id'].astype(np.int64)\nmean_correct = (labels['correct'].mean()*100).astype(np.float64)\n\nlabels_perc = labels.groupby('question_number')['correct'].value_counts(normalize=True).mul(100).rename('Percent').reset_index()\nlabels_perc['number'] = labels_perc['question_number'].apply(lambda x: int(x[1:])).astype(np.int64)\nlabels_perc = labels_perc[labels_perc['correct'] == 1]\npal = sns.color_palette(\"RdYlGn\", len(labels_perc))\nrank = labels_perc.sort_values('number')['Percent'].argsort().argsort()\nplt.figure(figsize=(20, 10))\ng = sns.barplot(data=labels_perc, x='number', y=\"Percent\", palette=np.array(pal[::])[rank])\ng.axhline(mean_correct, color=\"coral\")\nplt.text(9, mean_correct+1, f'Average ={round(mean_correct, 1)}%')\ng.axes.set_title(\"Share of correct answers by questions\", fontsize=18)\ng.set(xlabel='Question number', ylabel='Percent of correct answers')\ng.bar_label(g.containers[0], fmt=\"%.1f%%\")\ndel g, labels_perc, pal, rank, mean_correct","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:20.217536Z","iopub.execute_input":"2023-03-27T16:18:20.218060Z","iopub.status.idle":"2023-03-27T16:18:22.129641Z","shell.execute_reply.started":"2023-03-27T16:18:20.218027Z","shell.execute_reply":"2023-03-27T16:18:22.128652Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"correct_answers_per_session = labels.groupby('session_id_id')['correct'].sum()\nmean_correct_answers_per_session = correct_answers_per_session.mean()\nmedian_correct_answers_per_session = correct_answers_per_session.median()\ncorrect_answers_per_session = correct_answers_per_session.value_counts()\nplt.figure(figsize=(20, 8))\ng = sns.barplot(x=correct_answers_per_session.index, y=correct_answers_per_session.values, color='rosybrown')\nplt.title('Distribution of games by number of correct answers', fontsize=18)\ng.set_xticklabels(['{}'.format(int(num + 1)) for num in g.get_xticks()])\ng.set(xlabel='Number of correct answers', ylabel='Count of sessions')\ng.axvline(x=mean_correct_answers_per_session-1, color=\"coral\")\ng.text(mean_correct_answers_per_session-1, 1500, f'Average ={round(mean_correct_answers_per_session, 1)}', rotation=90)\ng.axvline(x=median_correct_answers_per_session-1, color=\"peru\")\ng.text(median_correct_answers_per_session-1, 1500, f'Median ={round(median_correct_answers_per_session, 1)}', rotation=90)\nfor i, v in enumerate(correct_answers_per_session.sort_index().values):\n    plt.text(i-0.2, v, str(int(v)))\ndel correct_answers_per_session, mean_correct_answers_per_session, median_correct_answers_per_session, i, v\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:22.130948Z","iopub.execute_input":"2023-03-27T16:18:22.131506Z","iopub.status.idle":"2023-03-27T16:18:22.707700Z","shell.execute_reply.started":"2023-03-27T16:18:22.131469Z","shell.execute_reply":"2023-03-27T16:18:22.706472Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del labels\ngc.collect()","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:22.709549Z","iopub.execute_input":"2023-03-27T16:18:22.710234Z","iopub.status.idle":"2023-03-27T16:18:22.845905Z","shell.execute_reply.started":"2023-03-27T16:18:22.710198Z","shell.execute_reply":"2023-03-27T16:18:22.844764Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 2. Exploring data <a class=\"anchor\" id=\"exploring-data\"></a>\n\nJo Wilder game is an example of the point-and-click genre. To progress through a game, the player must find hidden objects and/or answer quizzes.\n\nData matched to the game setup:\n- each game is a session defined by `session_id`\n- each row in the data is a game event, defined by `name`, type (`event_name`), unique ID (`fqid`), and game progress index (`index`)\n- the player progresses by moving from one game room (`room_fqid`) and level (`level`) to another\n- remaining columns refer to specific events:\n    - for click events, the coordinates of the click are defined in `room_coor_x` and `room_coor_y` (in reference to the in-game room) or in `screen_coor_x` and `screen_coor_y` (in reference to the player’s screen).\n    - for notebook-related events `page` identifies the page number;\n    - for hover events `hover_duration` shows how long was the hover.\n\nYou can play the game here: https://pbswisconsineducation.org/jowilder/play-the-game/\n\nThe game code source is public: https://github.com/fielddaylab/jo_wilder","metadata":{}},{"cell_type":"markdown","source":"[![Introducing Jo Wilder and the Capitol Case](https://i.imgur.com/EUpzSHK.png)](http://www.youtube.com/watch?v=GCBRRmm7T9k \"Introducing Jo Wilder and the Capitol Case\")","metadata":{}},{"cell_type":"markdown","source":"### 2.1 session_id <a class=\"anchor\" id=\"session-id\"></a>\n\n`session_id` is the ID of the session the event took place in. We have 11779 unique sessions in the train, but only 3 in the test.","metadata":{}},{"cell_type":"code","source":"train_events_counts = train['session_id'].value_counts()\ntest_events_counts = test['session_id'].value_counts()\n\nstat = pd.DataFrame([train_events_counts.describe().index,\n                    np.round(train_events_counts.describe(), 2).values,\n                    np.round(test_events_counts.describe(), 2).values]).T\nstat.columns = [' ', 'train', 'test']\nstat[['train', 'test']] = stat[['train', 'test']].astype(np.float64)\nprint(f'Unique sessions in train: {len(train_events_counts)}')\nprint(f'Unique sessions in test: {len(test_events_counts)}')\nprint('\\n\\nStatistics of events per session:')\nstat[1:].style.hide_index().format(precision=1).background_gradient(cmap='YlGn')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:22.847254Z","iopub.execute_input":"2023-03-27T16:18:22.847783Z","iopub.status.idle":"2023-03-27T16:18:23.065086Z","shell.execute_reply.started":"2023-03-27T16:18:22.847750Z","shell.execute_reply":"2023-03-27T16:18:23.064102Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"mean_train_events_counts = train_events_counts.mean()\nmedian_train_events_counts = train_events_counts.median()\nplt.figure(figsize=(20, 10))\ng = sns.histplot(data=train_events_counts)\nplt.title('Number of events per session for train (outliers with > 4000 excluded)', fontsize=18)\ng.set(xlabel='Number of events per session', ylabel='Count of sessions')\nplt.axvline(x=mean_train_events_counts, color=\"coral\")\nplt.text(mean_train_events_counts, 500, f'Average ={round(mean_train_events_counts, 1)}', rotation=90)\nplt.axvline(x=median_train_events_counts, color=\"peru\")\nplt.text(median_train_events_counts, 500, f'Median ={round(median_train_events_counts, 1)}', rotation=90)\nplt.xlim(600, 4000)\nplt.show()\ndel train_events_counts, mean_train_events_counts, median_train_events_counts, test_events_counts, stat, g","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:23.066379Z","iopub.execute_input":"2023-03-27T16:18:23.066904Z","iopub.status.idle":"2023-03-27T16:18:24.717475Z","shell.execute_reply.started":"2023-03-27T16:18:23.066872Z","shell.execute_reply":"2023-03-27T16:18:24.716270Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"For train, the distribution seems to be normal with a slight positive skew. The mean and median number of events per session are around 1100. For the test there are only 3 unique sessions, so the same analysis makes no sense.\n\n\nNext, as noticed by [@pdnartreb](https://www.kaggle.com/pdnartreb) in their [notebook](https://www.kaggle.com/code/pdnartreb/session-id-reverse-engineering), `session_id` likely contains date and time information. Let's review this metadata:","metadata":{}},{"cell_type":"code","source":"def get_session_id_features(data):\n    session_ids = pd.DataFrame()\n    session_ids['session_id'] = data['session_id'].unique()\n    session_ids['year'] = session_ids['session_id'].apply(lambda x: int(str(x)[:2])).astype(np.uint8)\n    session_ids['month'] = session_ids['session_id'].apply(lambda x: int(str(x)[2:4]) + 1).astype(np.uint8)\n    session_ids['weekday'] = session_ids['session_id'].apply(lambda x: int(str(x)[4:6])).astype(np.uint8)\n    session_ids['hour'] = session_ids['session_id'].apply(lambda x: int(str(x)[6:8])).astype(np.uint8)\n    session_ids['minute'] = session_ids['session_id'].apply(lambda x: int(str(x)[8:10])).astype(np.uint8)\n    session_ids['second'] = session_ids['session_id'].apply(lambda x: int(str(x)[10:12])).astype(np.uint8)\n    session_ids['ms'] = session_ids['session_id'].apply(lambda x: int(str(x)[12:15])).astype(np.uint16)\n    session_ids['unknown_part'] = session_ids['session_id'].apply(lambda x: int(str(x)[15:17])).astype(np.uint8)\n    return session_ids\n\ntrain_session_ids = get_session_id_features(train)\ntest_session_ids = get_session_id_features(test)\ntrain_session_ids.head()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:24.719316Z","iopub.execute_input":"2023-03-27T16:18:24.720041Z","iopub.status.idle":"2023-03-27T16:18:25.088385Z","shell.execute_reply.started":"2023-03-27T16:18:24.720005Z","shell.execute_reply":"2023-03-27T16:18:25.087444Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_session_ids['year'] = train_session_ids['year'] + 2000\ntrain_session_ids['date'] = (train_session_ids['year']).astype(str) + '-' + (train_session_ids['month']).astype(str)\nplt.figure(figsize=(20, 8))\ng = sns.countplot(x='date', data=train_session_ids)\nplt.title('Number of sessions for each month in train', fontsize=18)\ng.set_yticklabels(['{}'.format(int(num)) for num in g.get_yticks()])\ng.bar_label(g.containers[0])\ng.xaxis.set_tick_params(rotation=45)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:25.089795Z","iopub.execute_input":"2023-03-27T16:18:25.090341Z","iopub.status.idle":"2023-03-27T16:18:25.784810Z","shell.execute_reply.started":"2023-03-27T16:18:25.090308Z","shell.execute_reply":"2023-03-27T16:18:25.783167Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"So, our train data from Oct 2020 to Nov 2022.","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(15, 5))\ng = sns.countplot(x='weekday', data=train_session_ids)\nplt.title('Number of sessions for each weekday in train', fontsize=18)\ng.set_yticklabels(['{}'.format(int(num)) for num in g.get_yticks()])\ng.bar_label(g.containers[0])\ng.xaxis.set_tick_params()\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:25.786322Z","iopub.execute_input":"2023-03-27T16:18:25.786863Z","iopub.status.idle":"2023-03-27T16:18:26.024027Z","shell.execute_reply.started":"2023-03-27T16:18:25.786829Z","shell.execute_reply":"2023-03-27T16:18:26.022877Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(20, 8))\ng = sns.countplot(x='hour', data=train_session_ids)\nplt.title('Number of sessions for each hour in train', fontsize=18)\ng.set_yticklabels(['{}'.format(int(num)) for num in g.get_yticks()])\ng.bar_label(g.containers[0])\ng.xaxis.set_tick_params()\nplt.show()\ndel train_session_ids, g","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:26.025623Z","iopub.execute_input":"2023-03-27T16:18:26.025942Z","iopub.status.idle":"2023-03-27T16:18:26.588955Z","shell.execute_reply.started":"2023-03-27T16:18:26.025912Z","shell.execute_reply":"2023-03-27T16:18:26.588038Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Just as expected, the game is mostly played during the school days (monday to friday) and working hours (from 8 A.M. to 18 P.M.).","metadata":{}},{"cell_type":"markdown","source":"### 2.2 index <a class=\"anchor\" id=\"index\"></a>\n\n`index` is the index of the event for the session. Despite naive expectation, combination of `session_id` and `index` is not unique, there are duplicates:","metadata":{}},{"cell_type":"code","source":"idx = train[['session_id', 'index']]\nidx_dupl = idx[idx.duplicated()]\nprint(f\"\\nNumber of duplicated combinations session_id/index: {len(idx_dupl)}\")\nprint(f\"\\nNumber of sessions with duplicates: {len(idx_dupl['session_id'].unique())}\")\nprint(\"\\nExamples of sessions with duplicates:\", idx_dupl['session_id'].unique()[:4])","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:26.590075Z","iopub.execute_input":"2023-03-27T16:18:26.590687Z","iopub.status.idle":"2023-03-27T16:18:35.589166Z","shell.execute_reply.started":"2023-03-27T16:18:26.590647Z","shell.execute_reply":"2023-03-27T16:18:35.588290Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Here's an example of duplications:","metadata":{}},{"cell_type":"code","source":"del idx, idx_dupl\ntrain.loc[(train['session_id'] == '20110507081078290') & (train['index'].isin([624, 625]))].sort_values('index')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:35.593262Z","iopub.execute_input":"2023-03-27T16:18:35.595343Z","iopub.status.idle":"2023-03-27T16:18:35.765164Z","shell.execute_reply.started":"2023-03-27T16:18:35.595304Z","shell.execute_reply":"2023-03-27T16:18:35.764225Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Furthermore, some sessions have no `index == 0` (start of the game):","metadata":{}},{"cell_type":"code","source":"non_zero_index = train.groupby('session_id').filter(lambda x: 0 not in x[\"index\"].values)['session_id'].unique()\nprint(f\"Number session_id without index==0: {len(non_zero_index)}\")\nprint(\"\\nExamples of sessions without index==0:\", non_zero_index[:4])","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:35.766713Z","iopub.execute_input":"2023-03-27T16:18:35.767431Z","iopub.status.idle":"2023-03-27T16:18:44.118613Z","shell.execute_reply.started":"2023-03-27T16:18:35.767395Z","shell.execute_reply":"2023-03-27T16:18:44.117726Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The competition host [states](https://www.kaggle.com/competitions/predict-student-performance-from-game-play/discussion/384342#2134312) that all of this might be due to data errors:\n\n> These are both data errors in elapsed_time and index, which may be partly due to timestamps on the server sometimes being inaccurate if a student is clicking or performing other actions very quickly. You can assume that the index gives the correct order of events in cases where the elapsed_time is inconsistent. I'm less sure why the duplicates are occurring, but index == 0 should indicate the start of a session and there should not be duplicate events for a session.\n\nFurthermore, there are some other irregularities in the `index` data, described in [this notebook](https://www.kaggle.com/code/abaojiang/eda-on-game-progress) and summarized in [this discussion](https://www.kaggle.com/competitions/predict-student-performance-from-game-play/discussion/395250). Most or even all of them are present in the hidden test data, used for evaluation.","metadata":{}},{"cell_type":"markdown","source":"### 2.3 elapsed_time <a class=\"anchor\" id=\"elapsed-time\"></a>\n\n`elapsed_time`: shows how much time has passed (in milliseconds) between the start of the session and when the event was recorded","metadata":{}},{"cell_type":"code","source":"elapsed_time_train = np.round((train['elapsed_time']).astype(np.float64)/60000.0, 1)\nelapsed_time_test = np.round((test['elapsed_time']).astype(np.float64)/60000.0, 1)\n\nstat = pd.DataFrame([elapsed_time_train.describe().index,\n                    np.round(elapsed_time_train.describe(), 2).values,\n                    np.round(elapsed_time_test.describe(), 2).values]).T\nstat.columns = [' ', 'train', 'test']\nstat[['train', 'test']] = stat[['train', 'test']].astype(np.float64)\nprint('Elapsed time statistics for events:')\nstat[1:].style.hide_index().format(precision=1).background_gradient(cmap='YlGn')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:44.119874Z","iopub.execute_input":"2023-03-27T16:18:44.120436Z","iopub.status.idle":"2023-03-27T16:18:46.264133Z","shell.execute_reply.started":"2023-03-27T16:18:44.120401Z","shell.execute_reply":"2023-03-27T16:18:46.263210Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"mean_elapsed_time_train = elapsed_time_train.mean()\nmedian_elapsed_time_train = elapsed_time_train.median()\nelapsed_time_train_counts = np.round(elapsed_time_train, 0).astype(np.int64).value_counts()\n\nplt.figure(figsize=(20, 10))\ng = sns.barplot(x=elapsed_time_train_counts.index, y=elapsed_time_train_counts.values, color='limegreen')\nplt.title('Elapsed time for events in train (outliers not shown)', fontsize=18)\ng.set_xticklabels(['{}'.format(int(num)) if i % 10 == 0 else '' for i, num in enumerate(g.get_xticks())])\ng.set(xlabel='elapsed_time, minutes', ylabel='Events')\ng.axvline(x=mean_elapsed_time_train, color=\"coral\")\ng.text(mean_elapsed_time_train, 600000, f'Average ={round(mean_elapsed_time_train, 1)}', rotation=90)\ng.axvline(x=median_elapsed_time_train, color=\"peru\")\ng.text(median_elapsed_time_train, 600000, f'Median ={round(median_elapsed_time_train, 1)}', rotation=90)\ndel mean_elapsed_time_train, median_elapsed_time_train, elapsed_time_train_counts\nplt.xlim(0, 180)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:18:46.272026Z","iopub.execute_input":"2023-03-27T16:18:46.272766Z","iopub.status.idle":"2023-03-27T16:19:13.511021Z","shell.execute_reply.started":"2023-03-27T16:18:46.272729Z","shell.execute_reply":"2023-03-27T16:19:13.510188Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"mean_elapsed_time_test = elapsed_time_test.mean()\nmedian_elapsed_time_test = elapsed_time_test.median()\nelapsed_time_test_counts  = np.round(elapsed_time_test, 0).astype(np.int64).value_counts()\n\nplt.figure(figsize=(20, 10))\ng = sns.barplot(x=elapsed_time_test_counts.index, y=elapsed_time_test_counts.values, color='lightgreen')\nplt.title('Elapsed time for events in test', fontsize=18)\ng.set_xticklabels(['{}'.format(int(num)) if i % 10 == 0 else '' for i, num in enumerate(g.get_xticks())])\ng.set(xlabel='elapsed_time, minutes', ylabel='Events')\ng.axvline(x=mean_elapsed_time_test, color=\"coral\")\ng.text(mean_elapsed_time_test, 80, f'Average ={round(mean_elapsed_time_test, 1)}', rotation=90)\ng.axvline(x=median_elapsed_time_test, color=\"peru\")\ng.text(median_elapsed_time_test, 80, f'Median ={round(median_elapsed_time_test, 1)}', rotation=90)\ndel mean_elapsed_time_test, median_elapsed_time_test, elapsed_time_test_counts\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:19:13.512532Z","iopub.execute_input":"2023-03-27T16:19:13.513132Z","iopub.status.idle":"2023-03-27T16:19:14.329010Z","shell.execute_reply.started":"2023-03-27T16:19:13.513096Z","shell.execute_reply":"2023-03-27T16:19:14.328207Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Finally, we're also interested in the maximum `elapsed_time` for each session (i.e. when the game ended)","metadata":{}},{"cell_type":"code","source":"max_elapsed_time = train.groupby('session_id')['elapsed_time'].max()\nmax_elapsed_time = np.round(max_elapsed_time / 60000.0, 1)\n\nmean_elapsed_time = max_elapsed_time.mean()\nmedian_elapsed_time = max_elapsed_time.median()\nplt.figure(figsize=(20, 10))\ng = sns.histplot(max_elapsed_time, kde=False, color='mistyrose')\nplt.title('Maximum elapsed time for sessions in train (outliers not shown)', fontsize=18)\nplt.ticklabel_format(style='plain', axis='x')\ng.set(xlabel='Elapsed time, minutes', ylabel='Sessions')\nplt.axvline(x=mean_elapsed_time, color=\"coral\")\nplt.text(mean_elapsed_time, 600, f'Average ={round(mean_elapsed_time, 1)}', rotation=90)\nplt.axvline(x=median_elapsed_time, color=\"peru\")\nplt.text(median_elapsed_time, 600, f'Median ={round(median_elapsed_time, 1)}', rotation=90)\nplt.xlim(0, 360)\ndel max_elapsed_time, mean_elapsed_time, median_elapsed_time\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:19:14.330495Z","iopub.execute_input":"2023-03-27T16:19:14.331087Z","iopub.status.idle":"2023-03-27T16:19:49.924679Z","shell.execute_reply.started":"2023-03-27T16:19:14.331043Z","shell.execute_reply":"2023-03-27T16:19:49.923796Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Despite expectations, `elapsed_time` does not monotonically increase:","metadata":{}},{"cell_type":"code","source":"train['elapsed_time_change'] = (train.groupby('session_id')['elapsed_time'].diff()).astype(np.float32)\nprint(f\"Number of events with negative elapsed time change: {len(train[train['elapsed_time_change'] < 0])}\")\nprint(f\"Percent of events with negative elapsed time change: {round(len(train[train['elapsed_time_change'] < 0])/len(train)*100.0, 2)}\\n\")\nprint(f\"Number of sessions with at least one negative elapsed time change: {len(train.loc[train['elapsed_time_change'] < 0, 'session_id'].unique())}\")\nprint(f\"Percent of sessions with at least one negative elapsed time change: {round(len(train.loc[train['elapsed_time_change'] < 0, 'session_id'].unique())*100.0/len(train['session_id'].unique()), 2)}\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:19:49.926089Z","iopub.execute_input":"2023-03-27T16:19:49.926734Z","iopub.status.idle":"2023-03-27T16:20:06.550274Z","shell.execute_reply.started":"2023-03-27T16:19:49.926682Z","shell.execute_reply":"2023-03-27T16:20:06.548428Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"elapsed_time_change = np.round(train[train['elapsed_time_change'].notna()]['elapsed_time_change']/1000.0, 1)\nmean_elapsed_time_change = elapsed_time_change.mean()\nmedian_elapsed_time_change = elapsed_time_change.median()\nelapsed_time_change_counts = (np.round(elapsed_time_change, 0).astype(np.int64).value_counts()/1000).sort_index()\nplt.figure(figsize=(20, 10))\ng = sns.barplot(x=elapsed_time_change_counts.index, y=elapsed_time_change_counts.values, color='thistle')\nplt.title('elapsed_time change for events in train (outliers not shown)', fontsize=18)\ng.set(xlabel='elapsed_time_change, seconds', ylabel='Count, thousands')\ng.axvline(x=mean_elapsed_time_change+14, color=\"coral\")\ng.text(mean_elapsed_time_change+14, 5000, f'Average ={round(mean_elapsed_time_change, 1)}', rotation=90)\ng.axvline(x=median_elapsed_time_change+14, color=\"peru\")\ng.text(median_elapsed_time_change+14, 5000, f'Median ={round(median_elapsed_time_change, 1)}', rotation=90)\ndel mean_elapsed_time_change, median_elapsed_time_change, elapsed_time_change_counts, elapsed_time_change\ngc.collect()\nplt.xlim(5, 30)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:20:06.551749Z","iopub.execute_input":"2023-03-27T16:20:06.552075Z","iopub.status.idle":"2023-03-27T16:20:42.496132Z","shell.execute_reply.started":"2023-03-27T16:20:06.552045Z","shell.execute_reply":"2023-03-27T16:20:42.494992Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The bug with negative time change seems to [be present](https://www.kaggle.com/competitions/predict-student-performance-from-game-play/discussion/395686) in the hidden test data, used for evaluation.","metadata":{}},{"cell_type":"markdown","source":"### 2.4 event properties: name, type, and id <a class=\"anchor\" id=\"event-properties\"></a>\n\nLet's now look at event properties:\n- `event_name` is the event type (e.g. person click, cutscene click, object hover, etc)\n- `name` is the event name (e.g. identifies whether a notebook_click is opening or closing the notebook)\n- `fqid`: the fully qualified ID of the event (it refers to the person/object the player is interacting with)","metadata":{}},{"cell_type":"code","source":"event_names_counts_train = train['event_name'].value_counts()/1000\nnames_counts_train = train['name'].value_counts()/1000\nfqid_counts_train = train['fqid'].value_counts()\nevent_names_counts_test = test['event_name'].value_counts()\nnames_counts_test = test['name'].value_counts()\nfqid_counts_test = test['fqid'].value_counts()\n\nprint(\"Number of unique event names:\", len(event_names_counts_train))\nprint(\"Number of unique names:\", len(names_counts_train))\nprint(\"Number of unique fqid:\", len(fqid_counts_train))","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:20:42.497901Z","iopub.execute_input":"2023-03-27T16:20:42.498280Z","iopub.status.idle":"2023-03-27T16:20:43.078116Z","shell.execute_reply.started":"2023-03-27T16:20:42.498246Z","shell.execute_reply":"2023-03-27T16:20:43.076879Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(20, 9))\n\nplt.subplot(1, 2, 1)\nplt.bar(event_names_counts_train.index, event_names_counts_train.values, color='beige')\nplt.ylabel(\"Thousands of events\", fontsize=12)\nplt.title(\"Frequency of 'event_name' (=type) in train\", fontsize=18)\nfor i, v in enumerate(event_names_counts_train.values):\n    plt.text(i-0.4, v, str(round(v, 1)))\nplt.xticks(rotation=45)\n\nplt.subplot(1, 2, 2)\nplt.bar(event_names_counts_test.index, event_names_counts_test.values, color='cornsilk')\nplt.ylabel(\"Events\", fontsize=12)\nplt.title(\"Frequency of 'event_name' (=type) in test\", fontsize=18)\nfor i, v in enumerate(event_names_counts_test.values):\n    plt.text(i-0.4, v, str(int(v)))\nplt.xticks(rotation=45)\n\ndel i, v\nplt.show()\n","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:20:43.079643Z","iopub.execute_input":"2023-03-27T16:20:43.080307Z","iopub.status.idle":"2023-03-27T16:20:43.721962Z","shell.execute_reply.started":"2023-03-27T16:20:43.080268Z","shell.execute_reply":"2023-03-27T16:20:43.721116Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Meaning of the `event_name`:\n- *navigate_click*: click on the place we want move protagonist (Jo) to move;\n- *observation_click*: click on the black object that appears when you take your mouse pointer close to them;\n- *notification_click*: after navigate click on an object like the notebook, the retirement letter - the click to hide or to continue after the text appears;\n- *object_click*: click anywhere on the object pop up (only after notification_click)\n- *object_hover*: the student takes the mouse pointer above an object;\n- *map_hover*: the student takes the pointer above any place in the map;\n- *map_click*: click on a place on the map to go there;\n- *notebook_click*: click on notebook to look for notes made by Jo;\n- *checkpoint*: the last event of a level-group/chapter in the data.\n\nFor additional details see [this kernel](https://www.kaggle.com/code/shashwatraman/meaning-of-each-event-name-and-eda).","metadata":{}},{"cell_type":"code","source":"event_name_session_counts = pd.DataFrame(train.groupby('event_name')['session_id'].value_counts())\nevent_name_session_counts.columns = ['count']\nevent_name_session_counts = event_name_session_counts.reset_index(drop=False)\nprint(\"'event_name' counts per session in train:\")\nevent_name_session_counts.groupby('event_name')['count'].describe().drop(columns=['count']).style.format(precision=2).background_gradient(cmap='YlGn')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:20:43.723296Z","iopub.execute_input":"2023-03-27T16:20:43.724369Z","iopub.status.idle":"2023-03-27T16:20:56.563196Z","shell.execute_reply.started":"2023-03-27T16:20:43.724330Z","shell.execute_reply":"2023-03-27T16:20:56.562188Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del event_name_session_counts\nplt.figure(figsize=(20, 7))\n\nplt.subplot(1, 2, 1)\nplt.bar(names_counts_train.index, names_counts_train.values, color='honeydew')\nplt.ylabel(\"Thousands of events\", fontsize=12)\nplt.title(\"Frequency of 'name' in train\", fontsize=18)\nfor i, v in enumerate(names_counts_train.values):\n    plt.text(i-0.3, v, str(round(v, 1)))\nplt.xticks(rotation=45)\n\nplt.subplot(1, 2, 2)\nplt.bar(names_counts_test.index, names_counts_test.values, color='aliceblue')\nplt.ylabel(\"Events\", fontsize=12)\nplt.title(\"Frequency of 'name' in test\", fontsize=18)\nfor i, v in enumerate(names_counts_test.values):\n    plt.text(i-0.2, v, str(int(v)))\nplt.xticks(rotation=45)\n\ndel i, v\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:20:56.564493Z","iopub.execute_input":"2023-03-27T16:20:56.565519Z","iopub.status.idle":"2023-03-27T16:20:56.998483Z","shell.execute_reply.started":"2023-03-27T16:20:56.565475Z","shell.execute_reply":"2023-03-27T16:20:56.997299Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"event_name_session_counts = pd.DataFrame(train.groupby(['event_name', 'name'])['session_id'].value_counts())\nevent_name_session_counts.columns = ['count']\nevent_name_session_counts = event_name_session_counts.reset_index(drop=False)\nprint(\"'event_name'/'name' counts per session in train:\")\nevent_name_session_counts.groupby(['event_name', 'name'])['count'].describe().drop(columns=['count']).style.format(precision=2).background_gradient(cmap='YlGn')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:20:57.000491Z","iopub.execute_input":"2023-03-27T16:20:57.001691Z","iopub.status.idle":"2023-03-27T16:21:11.361470Z","shell.execute_reply.started":"2023-03-27T16:20:57.001645Z","shell.execute_reply":"2023-03-27T16:21:11.360564Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del event_name_session_counts\npivot = train.pivot_table(index='event_name', columns='name', aggfunc='size')\npivot = (pivot.fillna(0) / 1000).round(decimals = 1)\nplt.figure(figsize=(20, 8))\nannotations = pivot.astype(str)\nannotations[pivot == 0] = \"\"\ng = sns.heatmap(pivot, annot=annotations, fmt='', cmap='YlGn')\nplt.title(\"Frequency of 'event_name' vs 'name' in train, thousands\", fontsize=16)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:11.362785Z","iopub.execute_input":"2023-03-27T16:21:11.363897Z","iopub.status.idle":"2023-03-27T16:21:12.768725Z","shell.execute_reply.started":"2023-03-27T16:21:11.363860Z","shell.execute_reply":"2023-03-27T16:21:12.767479Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pivot = test.pivot_table(index='event_name', columns='name', aggfunc='size')\npivot = pivot.fillna(0).astype(int)\nplt.figure(figsize=(20, 8))\nannotations = pivot.astype(str)\nannotations[pivot == 0] = \"\"\ng = sns.heatmap(pivot, annot=annotations, fmt='', cmap='YlGn')\nplt.title(\"Frequency of 'event_name' vs 'name' in train data\", fontsize=16)\nplt.show()\ndel g, pivot, annotations","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:12.770000Z","iopub.execute_input":"2023-03-27T16:21:12.770453Z","iopub.status.idle":"2023-03-27T16:21:13.233230Z","shell.execute_reply.started":"2023-03-27T16:21:12.770420Z","shell.execute_reply":"2023-03-27T16:21:13.232039Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"word_freq_train = dict(zip(fqid_counts_train.index.tolist(), fqid_counts_train.values.tolist()))\nwordcloud_train = WordCloud(width=2000, height=1000, background_color=\"white\").generate_from_frequencies(word_freq_train)\nplt.figure(figsize=(20, 10))\nplt.imshow(wordcloud_train, interpolation='bilinear')\nplt.title(\"Most frequent fqid values in train\", fontsize=16)\nplt.axis(\"off\")\ndel word_freq_train, wordcloud_train\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:13.234726Z","iopub.execute_input":"2023-03-27T16:21:13.235230Z","iopub.status.idle":"2023-03-27T16:21:17.141960Z","shell.execute_reply.started":"2023-03-27T16:21:13.235174Z","shell.execute_reply":"2023-03-27T16:21:17.133428Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"word_freq_test = dict(zip(fqid_counts_test.index.tolist(), fqid_counts_test.values.tolist()))\nwordcloud_test = WordCloud(width=2000, height=1000, background_color=\"white\").generate_from_frequencies(word_freq_test)\nplt.figure(figsize=(20, 10))\nplt.imshow(wordcloud_test, interpolation='bilinear')\nplt.title(\"Most frequent fqid values in test\", fontsize=16)\nplt.axis(\"off\")\ndel word_freq_test, wordcloud_test\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:17.143544Z","iopub.execute_input":"2023-03-27T16:21:17.143986Z","iopub.status.idle":"2023-03-27T16:21:20.607747Z","shell.execute_reply.started":"2023-03-27T16:21:17.143954Z","shell.execute_reply":"2023-03-27T16:21:20.606877Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print('TOP-20 fqid for events:')\nstat = pd.DataFrame([fqid_counts_train, fqid_counts_test]).T\nstat.columns = ['train', 'test']\nstat = stat.sort_values('train', ascending=False)\ndel event_names_counts_train, names_counts_train, fqid_counts_train, event_names_counts_test, names_counts_test, fqid_counts_test\nstat.head(20).style.format(precision=0)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:20.608886Z","iopub.execute_input":"2023-03-27T16:21:20.609889Z","iopub.status.idle":"2023-03-27T16:21:20.626387Z","shell.execute_reply.started":"2023-03-27T16:21:20.609851Z","shell.execute_reply":"2023-03-27T16:21:20.625517Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 2.5. game room <a class=\"anchor\" id=\"game-room\"></a>\n\n`room_fqid` is the ID of the room the event took place in. The player progresses through the game by moving from one room to another.","metadata":{}},{"cell_type":"code","source":"room_counts_train = train['room_fqid'].value_counts()/1000\nroom_counts_test = test['room_fqid'].value_counts()\n\nprint(\"Number of unique rooms:\", len(room_counts_train))","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:20.627409Z","iopub.execute_input":"2023-03-27T16:21:20.628256Z","iopub.status.idle":"2023-03-27T16:21:20.803073Z","shell.execute_reply.started":"2023-03-27T16:21:20.628221Z","shell.execute_reply":"2023-03-27T16:21:20.802234Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(20, 9))\nplt.bar(room_counts_train.index, room_counts_train.values, color='thistle')\nplt.ylabel(\"Thousands of events\", fontsize=12)\nplt.title(\"Frequency of rooms in train\", fontsize=18)\nfor i, v in enumerate(room_counts_train.values):\n    plt.text(i-0.35, v, str(round(v, 1)))\nplt.xticks(rotation=90)\nplt.show()\n\nplt.figure(figsize=(20, 9))\nplt.bar(room_counts_test.index, room_counts_test.values, color='pink')\nplt.ylabel(\"Events\", fontsize=12)\nplt.title(\"Frequency of rooms in test\", fontsize=18)\nfor i, v in enumerate(room_counts_test.values):\n    plt.text(i-0.2, v, str(int(v)))\nplt.xticks(rotation=90)\n\ndel i, v\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:20.804108Z","iopub.execute_input":"2023-03-27T16:21:20.805114Z","iopub.status.idle":"2023-03-27T16:21:21.781905Z","shell.execute_reply.started":"2023-03-27T16:21:20.805080Z","shell.execute_reply":"2023-03-27T16:21:21.780978Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 2.6 level and level_group <a class=\"anchor\" id=\"level\"></a>\n\n- `level` is what level of the game the event occurred in (0 to 22).\n- `level_group`: which group of levels - and group of questions - this row belongs to (0-4, 5-12, 13-22)\n\nThe number of questions in each level_group is fixed:\n\n| `level_group` | questions | number of questions|\n|:---:|:--:|:--:|\n| 0-4 |  q1 to q3 |3|\n| 5-12 | q4 to q13 |10|\n| 13-22 | q14 to q18 |5|\n\nAt levels 4, 12, 22 the game has *checkpoint* events when questions are being asked.\n\nNote that there are sessions in which one of the 22 levels is not present. Most likely this is due to data record issues:","metadata":{}},{"cell_type":"code","source":"unique_levels_per_session = train[['session_id', 'level']].drop_duplicates()\nmissing_sessions = unique_levels_per_session.groupby('session_id').count().reset_index(drop=False)\nmissing_sessions = missing_sessions[missing_sessions['level'] != 23].reset_index(drop=True)['session_id']\nprint(f\"Number of sessions in which one of the 22 levels is not present: {len(missing_sessions)}\")\nfor lvl in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]:\n    x = unique_levels_per_session.loc[(unique_levels_per_session['session_id'].isin(missing_sessions)) & (unique_levels_per_session['level'].eq(lvl))]\n    if len(x) != len(missing_sessions):\n        print(f\"\\nLevel {lvl} is missing in {len(x)} sessions\")\n        print(f\"Examples of sessions with missing {lvl} level:\", x['session_id'].to_list()[:5])\ndel lvl, x, unique_levels_per_session, missing_sessions\n\nlevel_counts_train = train['level'].value_counts()/1000\nlevel_group_counts_train = train['level_group'].value_counts()/1000\nlevel_counts_test = test['level'].value_counts()\nlevel_group_counts_test = test['level_group'].value_counts()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:21.783138Z","iopub.execute_input":"2023-03-27T16:21:21.784166Z","iopub.status.idle":"2023-03-27T16:21:23.551629Z","shell.execute_reply.started":"2023-03-27T16:21:21.784115Z","shell.execute_reply":"2023-03-27T16:21:23.550800Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(20, 10))\nplt.subplot(1, 3, (1, 2))\nplt.bar(level_counts_train.index, level_counts_train.values, color='lightsalmon')\nplt.ylabel(\"Thousands of events\", fontsize=12)\nplt.title(\"Frequency of level in train\", fontsize=18)\nfor i, v in enumerate(level_counts_train.sort_index().values):\n    plt.text(i-0.6, v, str(round(v, 1)))\nplt.xticks(level_counts_train.index)\n\nplt.subplot(1, 3, 3)\nplt.bar(level_group_counts_train.index, level_group_counts_train.values, color='peachpuff')\nplt.ylabel(\"Thousands of events\", fontsize=12)\nplt.title(\"Frequency of level_group in train\", fontsize=18)\nfor i, v in enumerate(level_group_counts_train):\n    plt.text(i-0.15, v, str(round(v, 1)))\nplt.xticks()\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:23.552690Z","iopub.execute_input":"2023-03-27T16:21:23.553310Z","iopub.status.idle":"2023-03-27T16:21:24.212901Z","shell.execute_reply.started":"2023-03-27T16:21:23.553276Z","shell.execute_reply":"2023-03-27T16:21:24.209053Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(20, 10))\nplt.subplot(1, 3, (1, 2))\nplt.bar(level_counts_test.index, level_counts_test.values, color='violet')\nplt.ylabel(\"Events\", fontsize=12)\nplt.title(\"Frequency of level in test\", fontsize=18)\nfor i, v in enumerate(level_counts_test.sort_index().values):\n    plt.text(i-0.35, v, str(int(v)))\nplt.xticks(level_counts_train.index)\n\nplt.subplot(1, 3, 3)\nplt.bar(level_group_counts_test.index, level_group_counts_test.values, color='thistle')\nplt.ylabel(\"Events\", fontsize=12)\nplt.title(\"Frequency of level_group in test\", fontsize=18)\nfor i, v in enumerate(level_group_counts_test):\n    plt.text(i-0.15, v, str(int(v)))\nplt.xticks()\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:24.214281Z","iopub.execute_input":"2023-03-27T16:21:24.214621Z","iopub.status.idle":"2023-03-27T16:21:24.868316Z","shell.execute_reply.started":"2023-03-27T16:21:24.214590Z","shell.execute_reply":"2023-03-27T16:21:24.867502Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's also look at intersections with some other variables:","metadata":{}},{"cell_type":"code","source":"pivot = train.pivot_table(index='room_fqid', columns='level', aggfunc='size')\npivot = (pivot.fillna(0) / 1000).round(decimals = 1)\nplt.figure(figsize=(20, 12))\nannotations = pivot.astype(str)\nannotations[pivot == 0] = \"\"\ng = sns.heatmap(pivot, annot=annotations, fmt='', cmap='YlGn')\nplt.title(\"Frequency of 'level' vs 'room_fqid' in train, thousands events\", fontsize=16)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:24.869392Z","iopub.execute_input":"2023-03-27T16:21:24.869919Z","iopub.status.idle":"2023-03-27T16:21:27.652908Z","shell.execute_reply.started":"2023-03-27T16:21:24.869888Z","shell.execute_reply":"2023-03-27T16:21:27.651824Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pivot = train.pivot_table(index='event_name', columns='level', aggfunc='size')\npivot = (pivot.fillna(0) / 1000).round(decimals = 1)\nplt.figure(figsize=(20, 9))\nannotations = pivot.astype(str)\nannotations[pivot == 0] = \"\"\ng = sns.heatmap(pivot, annot=annotations, fmt='', cmap='YlGn')\nplt.title(\"Frequency of 'level' vs 'event_name' in train, thousands events\", fontsize=16)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:27.654252Z","iopub.execute_input":"2023-03-27T16:21:27.654652Z","iopub.status.idle":"2023-03-27T16:21:30.140843Z","shell.execute_reply.started":"2023-03-27T16:21:27.654622Z","shell.execute_reply":"2023-03-27T16:21:30.139840Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pivot = train.pivot_table(index='name', columns='level', aggfunc='size')\npivot = (pivot.fillna(0) / 1000).round(decimals = 1)\nplt.figure(figsize=(20, 6))\nannotations = pivot.astype(str)\nannotations[pivot == 0] = \"\"\ng = sns.heatmap(pivot, annot=annotations, fmt='', cmap='YlGn')\nplt.title(\"Frequency of 'level' vs 'name' in train, thousands events\", fontsize=16)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:30.142071Z","iopub.execute_input":"2023-03-27T16:21:30.142583Z","iopub.status.idle":"2023-03-27T16:21:32.268598Z","shell.execute_reply.started":"2023-03-27T16:21:30.142549Z","shell.execute_reply":"2023-03-27T16:21:32.267184Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 2.7 click geo-location <a class=\"anchor\" id=\"click-geo-location\"></a>\n\nThere are four geo-location variables in the data:\n- `room_coor_x`: the coordinates of the click in reference to the in-game room (only for click events)\n- `room_coor_y`: the coordinates of the click in reference to the in-game room (only for click events)\n- `screen_coor_x`: the coordinates of the click in reference to the player’s screen (only for click events)\n- `screen_coor_y`: the coordinates of the click in reference to the player’s screen (only for click events)\n\nAbout 90-92% of all events are the click events:","metadata":{}},{"cell_type":"code","source":"print(f\"Share of events with geo-location in train: {round(train['room_coor_x'].count() / len(train)*100, 2)}\")\nprint(f\"Share of events with geo-location in test: {round(test['room_coor_x'].count() / len(test)*100, 2)}\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:32.269794Z","iopub.execute_input":"2023-03-27T16:21:32.270524Z","iopub.status.idle":"2023-03-27T16:21:32.324701Z","shell.execute_reply.started":"2023-03-27T16:21:32.270491Z","shell.execute_reply":"2023-03-27T16:21:32.323537Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"stat = pd.DataFrame([train['room_coor_x'].describe().index.to_list(),\n                    train['room_coor_x'].astype('float64').describe().values,\n                    train['room_coor_y'].astype('float64').describe().values,\n                    train['screen_coor_x'].astype('float64').describe().values,\n                    train['screen_coor_y'].astype('float64').describe().values,\n                    test['room_coor_x'].astype('float64').describe().values,\n                    test['room_coor_y'].astype('float64').describe().values,\n                    test['screen_coor_x'].astype('float64').describe().values,\n                    test['screen_coor_y'].astype('float64').describe().values]).T\nstat.columns = [' ', 'room_coor_x in train', 'room_coor_y in train', 'screen_coor_x in train', 'screen_coor_y in train',\n                     'room_coor_x in test', 'room_coor_y in test', 'screen_coor_x in test', 'screen_coor_y in test']\nstat[stat.columns.to_list()[1:]] = stat[stat.columns.to_list()[1:]].apply(pd.to_numeric, errors='coerce' )\nprint('Geo-location statistics for events:')\nstat[1:].style.hide_index().format(precision=1).background_gradient(cmap='YlGn')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:32.325955Z","iopub.execute_input":"2023-03-27T16:21:32.326386Z","iopub.status.idle":"2023-03-27T16:21:40.589911Z","shell.execute_reply.started":"2023-03-27T16:21:32.326354Z","shell.execute_reply":"2023-03-27T16:21:40.588790Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"However, general coordinates statistics make little or no sense. We need click density for each specific room:","metadata":{}},{"cell_type":"code","source":"rooms = train['room_fqid'].unique().to_list()\nfor room in rooms:\n    data_room = train[train['room_fqid'] == room]\n    data_room = data_room[['room_coor_x', 'room_coor_y', 'screen_coor_x', 'screen_coor_y']].dropna().reset_index(drop=True)\n    data_room = data_room.apply(pd.to_numeric, errors='coerce')\n    g = sns.pairplot(\n        data=data_room,\n        x_vars=[\"room_coor_x\"],\n        y_vars=[\"room_coor_y\"],\n        kind='hist',\n        height=6\n    )\n    g.fig.suptitle(f\"{room}: game room coordinates\")\n    g.savefig('g0.png', dpi=300)\n    plt.close(g.fig)\n    \n    g = sns.pairplot(\n        data=data_room,\n        x_vars=[\"screen_coor_x\"],\n        y_vars=[\"screen_coor_y\"],\n        kind='hist',\n        height=6\n    )\n    g.fig.suptitle(f\"{room}: player's screen coordinates\")\n    g.savefig('g1.png', dpi=300)\n    plt.close(g.fig)\n    \n    f, ax = plt.subplots(1, 2, figsize=(20, 20))\n    ax[0].imshow(mpimg.imread('g0.png'))\n    ax[1].imshow(mpimg.imread('g1.png'))\n    [axarr.set_axis_off() for axarr in ax.ravel()]\n    plt.tight_layout()\n    plt.show()\n\ndel room, rooms, data_room, g, f, ax","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:21:40.591228Z","iopub.execute_input":"2023-03-27T16:21:40.591621Z","iopub.status.idle":"2023-03-27T16:23:24.446784Z","shell.execute_reply.started":"2023-03-27T16:21:40.591591Z","shell.execute_reply":"2023-03-27T16:23:24.445446Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"A high density of clicks shows the locations of important objects or characters. Now let's review the geo-location path for a randomly selected session:","metadata":{}},{"cell_type":"code","source":"def plot_geo_location(df, session_id):\n    session_df = df[df['session_id'] == session_id]\n    rooms = session_df['room_fqid'].unique().to_list()\n    for room in rooms:\n        session_df_room = session_df[session_df['room_fqid'] == room]\n        session_df_room = session_df_room[['room_coor_x', 'room_coor_y', 'screen_coor_x', 'screen_coor_y']].dropna().reset_index(drop=True)        \n        plt.figure(figsize=(15, 6))        \n        \n        # room\n        x = session_df_room['room_coor_x']\n        y = session_df_room['room_coor_y']\n        plt.subplot(1, 5, (1, 3))\n        plt.plot(x, y, zorder=0, lw=0.5, color='steelblue')\n        plt.scatter(x, y, s=5, color='grey')\n        plt.scatter(x[0], y[0], s=200, lw=5, color='gold', marker='*')\n        plt.scatter(x[-1:], y[-1:], s=200, lw=5, color='crimson', marker='*')\n        plt.title(f\"{session_id}: {room} (room)\")\n        plt.legend(['Cursor path', 'Click position', 'Start', 'End'])\n        plt.gca().set_aspect('equal', adjustable='box')\n        plt.xlim(-2000, 1300)\n        plt.ylim(-920, 550)\n        plt.xlabel(\"room_coor_x\")\n        plt.ylabel(\"room_coor_y\")      \n    \n        # screen\n        x = session_df_room['screen_coor_x']\n        y = session_df_room['screen_coor_y']\n        plt.subplot(1, 5, (4, 5))\n        plt.plot(x, y, zorder=0, lw=0.5, color='lightcoral')\n        plt.scatter(x, y, s=5, color='grey')\n        plt.scatter(x[0], y[0], s=200, lw=5, color='gold', marker='*')\n        plt.scatter(x[-1:], y[-1:], s=200, lw=5, color='crimson', marker='*')\n        plt.title(f\"session {session_id}: {room} (screen)\")\n        plt.legend(['Cursor path', 'Click position', 'Start', 'End'])\n        plt.gca().set_aspect('equal', adjustable='box')\n        plt.xlabel(\"screen_coor_x\")\n        plt.ylabel(\"screen_coor_y\")\n        plt.xlim(0, 2000)\n        plt.ylim(0, 1500)\n        plt.tight_layout()\n        plt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:24.448424Z","iopub.execute_input":"2023-03-27T16:23:24.448769Z","iopub.status.idle":"2023-03-27T16:23:24.464024Z","shell.execute_reply.started":"2023-03-27T16:23:24.448738Z","shell.execute_reply":"2023-03-27T16:23:24.463074Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"idx_dupl = train[['session_id', 'index']]\nidx_dupl = idx_dupl[idx_dupl.duplicated()]\nsession_ids = train['session_id'].unique()\nsession_ids = session_ids[np.isin(session_ids, idx_dupl['session_id'].unique(), invert=True)]\ndel idx_dupl\ngc.collect()\nplot_geo_location(train, np.random.choice(session_ids))\ndel session_ids","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:24.465394Z","iopub.execute_input":"2023-03-27T16:23:24.465735Z","iopub.status.idle":"2023-03-27T16:23:45.326819Z","shell.execute_reply.started":"2023-03-27T16:23:24.465704Z","shell.execute_reply":"2023-03-27T16:23:45.323360Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"For additional details on click flow see insightful [discussion](https://www.kaggle.com/competitions/predict-student-performance-from-game-play/discussion/387864) and [notebook](https://www.kaggle.com/code/cdeotte/game-room-click-eda) by Chris Deotte.\n\nIn addition, [this notebook](https://www.kaggle.com/code/vassylkorzh/play-game-session?scriptVersionId=119784646) has an interactive view on clicks: it allows you to select a session and watch the session click events at each stage of the game.","metadata":{}},{"cell_type":"markdown","source":"## 2.8 text <a class=\"anchor\" id=\"text\"></a>\n\nThere are two text variables:\n- `text`: the text the player sees during this event; some of them [are emoji](https://www.kaggle.com/competitions/predict-student-performance-from-game-play/discussion/393571) in hex format, e.g. `\\u00f0\\u0178\\u02dc\\u0090` corresponds to 😐\n- `text_fqid`: the fully qualified ID of the text","metadata":{}},{"cell_type":"code","source":"print(f\"Number of unique text messages in train: {len(train['text'].unique())}\")\nprint(f\"Number of unique text messages in test: {len(test['text'].unique())}\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:45.328044Z","iopub.execute_input":"2023-03-27T16:23:45.328858Z","iopub.status.idle":"2023-03-27T16:23:45.518041Z","shell.execute_reply.started":"2023-03-27T16:23:45.328821Z","shell.execute_reply":"2023-03-27T16:23:45.516979Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"text_counts_train = test['text'].value_counts()\nword_freq_train = dict(zip(text_counts_train.index.tolist(), text_counts_train.values.tolist()))\nwordcloud_train = WordCloud(width=2000, height=1000, background_color=\"white\").generate_from_frequencies(word_freq_train)\nplt.figure(figsize=(20, 10))\nplt.imshow(wordcloud_train, interpolation='bilinear')\nplt.title(\"Top text messages in train\", fontsize=16)\nplt.axis(\"off\")\ndel word_freq_train, wordcloud_train, text_counts_train\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:45.519686Z","iopub.execute_input":"2023-03-27T16:23:45.520021Z","iopub.status.idle":"2023-03-27T16:23:50.448877Z","shell.execute_reply.started":"2023-03-27T16:23:45.519989Z","shell.execute_reply":"2023-03-27T16:23:50.447562Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(f\"TOP-20 text messages:\")\nstat = pd.DataFrame([train['text'].value_counts(), test['text'].value_counts()]).T\nstat.columns = ['train', 'test']\nstat = stat.sort_values('train', ascending=False)\nstat.head(20).style.format(precision=0)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:50.450665Z","iopub.execute_input":"2023-03-27T16:23:50.451049Z","iopub.status.idle":"2023-03-27T16:23:50.636311Z","shell.execute_reply.started":"2023-03-27T16:23:50.451004Z","shell.execute_reply":"2023-03-27T16:23:50.635228Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"text_counts_train = test['text_fqid'].value_counts()\nword_freq_train = dict(zip(text_counts_train.index.tolist(), text_counts_train.values.tolist()))\nwordcloud_train = WordCloud(width=2000, height=1000, background_color=\"white\").generate_from_frequencies(word_freq_train)\nplt.figure(figsize=(20, 10))\nplt.imshow(wordcloud_train, interpolation='bilinear')\nplt.title(\"Top text_fqid values in train\", fontsize=16)\nplt.axis(\"off\")\ndel word_freq_train, wordcloud_train, text_counts_train\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:50.637901Z","iopub.execute_input":"2023-03-27T16:23:50.638265Z","iopub.status.idle":"2023-03-27T16:23:53.795584Z","shell.execute_reply.started":"2023-03-27T16:23:50.638233Z","shell.execute_reply":"2023-03-27T16:23:53.794450Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(f\"TOP-20 text_fqid messages:\")\nstat = pd.DataFrame([train['text_fqid'].value_counts(), test['text_fqid'].value_counts()]).T\nstat.columns = ['train', 'test']\nstat = stat.sort_values('train', ascending=False)\nstat.head(20).style.format(precision=0)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:53.797333Z","iopub.execute_input":"2023-03-27T16:23:53.797699Z","iopub.status.idle":"2023-03-27T16:23:53.969886Z","shell.execute_reply.started":"2023-03-27T16:23:53.797664Z","shell.execute_reply":"2023-03-27T16:23:53.968835Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Note that `text_fqid` and `text` are not the same feature:\n- `text_fqid` is the text group;\n- `text` is specific text shown to player;","metadata":{}},{"cell_type":"code","source":"print(f\"TOP-30 text_fqid and text combinations:\")\nstat = pd.DataFrame([train[['text_fqid', 'text']].value_counts(), test[['text_fqid', 'text']].value_counts()]).T\nstat.columns = ['train', 'test']\nstat = stat.sort_values('train', ascending=False)\nstat.head(30).style.format(precision=0)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:53.971752Z","iopub.execute_input":"2023-03-27T16:23:53.972091Z","iopub.status.idle":"2023-03-27T16:23:54.990340Z","shell.execute_reply.started":"2023-03-27T16:23:53.972060Z","shell.execute_reply":"2023-03-27T16:23:54.989311Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Furthermore, `text_fqid` includes two elements: `room_fqid` (game room) and `fqid` (person/object the player is interacting with). Examples:","metadata":{}},{"cell_type":"code","source":"train[['room_fqid', 'fqid', 'text_fqid']].dropna().drop_duplicates().head().style.hide_index()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:54.991800Z","iopub.execute_input":"2023-03-27T16:23:54.992115Z","iopub.status.idle":"2023-03-27T16:23:56.106963Z","shell.execute_reply.started":"2023-03-27T16:23:54.992086Z","shell.execute_reply":"2023-03-27T16:23:56.105860Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 2.9 page <a class=\"anchor\" id=\"page\"></a>\n\n`page` is the page number of the event (only for notebook-related events). Share of notebook-related events is small:","metadata":{}},{"cell_type":"code","source":"print(f\"Share of events with page in train: {round(train['page'].count() / len(train)*100, 2)}%\")\nprint(f\"Share of events with page in test: {round(test['page'].count() / len(test)*100, 2)}%\")\n\npage_counts_train = train['page'].value_counts()/1000\npage_counts_test = test['page'].value_counts()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:56.108310Z","iopub.execute_input":"2023-03-27T16:23:56.108713Z","iopub.status.idle":"2023-03-27T16:23:56.181045Z","shell.execute_reply.started":"2023-03-27T16:23:56.108684Z","shell.execute_reply":"2023-03-27T16:23:56.179938Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(20, 8))\nplt.subplot(1, 2, 1)\nplt.bar(page_counts_train.index, page_counts_train.values, color='skyblue')\nplt.ylabel(\"Thousands of events\", fontsize=12)\nplt.title(\"Frequency of page in train\", fontsize=18)\nfor i, v in enumerate(page_counts_train.values):\n    plt.text(i-0.25, v, str(round(v, 1)))\nplt.xticks()\n\nplt.subplot(1, 2, 2)\nplt.bar(page_counts_test.index, page_counts_test.values, color='lightblue')\nplt.ylabel(\"Events\", fontsize=12)\nplt.title(\"Frequency of page in test\", fontsize=18)\nfor i, v in enumerate(page_counts_test.values):\n    plt.text(i-0.15, v, str(int(v)))\ndel page_counts_train, page_counts_test\nplt.xticks()\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:56.182322Z","iopub.execute_input":"2023-03-27T16:23:56.182747Z","iopub.status.idle":"2023-03-27T16:23:56.611554Z","shell.execute_reply.started":"2023-03-27T16:23:56.182713Z","shell.execute_reply":"2023-03-27T16:23:56.610632Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 2.10 hover <a class=\"anchor\" id=\"hover\"></a>\n\n`hover_duration`: how long (in milliseconds) the hover happened for (only for hover events). There are [two types](https://www.kaggle.com/competitions/predict-student-performance-from-game-play/discussion/391043#2162552) of hover events:\n\n>*Object_Hover* - In each chapter of the game, some tasks have to be performed by the student, like clicking the slip on the t-shirt in the 1st Chapter of the game. This slip is an object. If the student takes the mouse pointer above this object, an object_hover is recorded.\nThe duration for which the pointer stays above this object, is recorded in the `hover_duration`.\nIf he/she clicks on the object, it's an `object_click`\n\n>*Map_Hover* - In the map, there are many places a student can click to go there. When he/she takes the pointer above any place in the map, a `map_hover` event is recorded.\nJust like in object_hover, hover_duration is the duration for which the pointer stays above that place.\nAnd when the student clicks on any place, it's a `map_click`\n\nShare of hover events is small:\n","metadata":{}},{"cell_type":"code","source":"print(f\"Share of events with hover_duration in train: {round(train['hover_duration'].count() / len(train)*100, 2)}%\")\nprint(f\"Share of events with hover_duration in test: {round(test['hover_duration'].count() / len(test)*100, 2)}%\")\n\nhover_duration_train = np.round(train[train['hover_duration'].notna()]['hover_duration'].astype(np.int32)/1000.0, 1)\nhover_duration_test = np.round(test[test['hover_duration'].notna()]['hover_duration'].astype(np.int32)/1000.0, 1)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:56.612703Z","iopub.execute_input":"2023-03-27T16:23:56.613748Z","iopub.status.idle":"2023-03-27T16:23:57.009892Z","shell.execute_reply.started":"2023-03-27T16:23:56.613709Z","shell.execute_reply":"2023-03-27T16:23:57.008946Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"stat = pd.DataFrame([hover_duration_train.describe().index.to_list(),\n                    hover_duration_train.describe().values,\n                    hover_duration_test.describe().values]).T\nstat.columns = [' ', 'hover_duration in train, seconds', 'hover_duration in test, seconds']\nstat[stat.columns.to_list()[1:]] = stat[stat.columns.to_list()[1:]].apply(pd.to_numeric, errors='coerce' )\nprint('hover_duration statistics:')\nstat[1:].style.hide_index().format(precision=2).background_gradient(cmap='YlGn')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:57.010969Z","iopub.execute_input":"2023-03-27T16:23:57.011966Z","iopub.status.idle":"2023-03-27T16:23:57.206631Z","shell.execute_reply.started":"2023-03-27T16:23:57.011915Z","shell.execute_reply":"2023-03-27T16:23:57.205803Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"mean_hover_duration_train = hover_duration_train.mean()\nmedian_hover_duration_train = hover_duration_train.median()\nhover_duration_train_counts = np.round(hover_duration_train*10, 0).astype(np.int64).value_counts()\n\nplt.figure(figsize=(20, 9))\ng = sns.barplot(x=hover_duration_train_counts.index, y=np.round(hover_duration_train_counts.values, 0), color='wheat')\nplt.title('hover_duration for events in train (outliers not shown)', fontsize=18)\ng.set_xticklabels(['{}'.format(int(num/10)) if i % 10 == 0 else '' for i, num in enumerate(g.get_xticks())])\ng.set(xlabel='hover_duration, seconds', ylabel='Count')\ng.axvline(x=mean_hover_duration_train*10, color=\"peachpuff\")\ng.text(mean_hover_duration_train*10, 150000, f'Average ={round(mean_hover_duration_train, 1)}', rotation=90)\nplt.axvline(x=median_hover_duration_train*10, color=\"peru\")\nplt.text(median_hover_duration_train*10, 150000, f'Median ={round(median_hover_duration_train, 1)}', rotation=90)\nplt.xlim(-1, 8*10)\ndel mean_hover_duration_train, median_hover_duration_train, hover_duration_train_counts\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:23:57.207644Z","iopub.execute_input":"2023-03-27T16:23:57.208675Z","iopub.status.idle":"2023-03-27T16:24:06.613245Z","shell.execute_reply.started":"2023-03-27T16:23:57.208640Z","shell.execute_reply":"2023-03-27T16:24:06.612183Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"mean_hover_duration_test = hover_duration_test.mean()\nmedian_hover_duration_test = hover_duration_test.median()\nhover_duration_test_counts = np.round(hover_duration_test*10, 0).astype(np.int64).value_counts()\n\nplt.figure(figsize=(20, 7))\ng = sns.barplot(x=hover_duration_test_counts.index, y=hover_duration_test_counts.values, color='wheat')\nplt.title('hover_duration for events in test', fontsize=18)\ng.set_xticklabels(['{}'.format(int(num/10)) if i % 10 == 0 else '' for i, num in enumerate(g.get_xticks())])\ng.set(xlabel='hover_duration, seconds', ylabel='Count')\ng.axvline(x=mean_hover_duration_test*10, color=\"coral\")\ng.text(mean_hover_duration_test*10, 30, f'Average ={round(mean_hover_duration_test, 1)}', rotation=90)\ng.axvline(x=median_hover_duration_test*10, color=\"peru\")\ng.text(median_hover_duration_test*10, 30, f'Median ={round(median_hover_duration_test, 1)}', rotation=90)\ndel mean_hover_duration_test, median_hover_duration_test, hover_duration_test_counts, hover_duration_train, hover_duration_test\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:24:06.614712Z","iopub.execute_input":"2023-03-27T16:24:06.615045Z","iopub.status.idle":"2023-03-27T16:24:07.365548Z","shell.execute_reply.started":"2023-03-27T16:24:06.615014Z","shell.execute_reply":"2023-03-27T16:24:07.362807Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 2.11 game properties: fullscreen, hq, and music <a class=\"anchor\" id=\"game-properties\"></a>\n\nFinally, we also have game properties:\n- `fullscreen` - whether the player is in fullscreen mode\n- `hq` - whether the game is in high-quality\n- `music` - whether the game music is on or off\n\nWe can assume that `0` in each of these columns corresponds to No, and `1` to Yes.\n\nAll of them were missing in the original train set, but appeared after [the data update](https://www.kaggle.com/competitions/predict-student-performance-from-game-play/discussion/396202).","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(20, 14))\nplt.subplot(1, 3, 1)\nplt.pie(train['fullscreen'].value_counts().sort_index(), labels = ['No (0)', 'Yes (1)'], colors = ['pink', 'lightgreen'], autopct='%.1f%%')\nplt.title('fullscreen, % of events', fontsize=18)\n\nplt.subplot(1, 3, 2)\nplt.pie(train['hq'].value_counts().sort_index(), labels = ['No (0)', 'Yes (1)'], colors = ['pink', 'lightgreen'], autopct='%.1f%%')\nplt.title('high-quality resolution (hq), % of events', fontsize=18)\n\nplt.subplot(1, 3, 3)\nplt.pie(train['music'].value_counts().sort_index(), labels = ['No (0)', 'Yes (1)'], colors = ['pink', 'lightgreen'], autopct='%.1f%%')\nplt.title('music, % of events', fontsize=18)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:24:07.366934Z","iopub.execute_input":"2023-03-27T16:24:07.367298Z","iopub.status.idle":"2023-03-27T16:24:08.189976Z","shell.execute_reply.started":"2023-03-27T16:24:07.367267Z","shell.execute_reply":"2023-03-27T16:24:08.189010Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It looks like game properties are the same from beginning to an end of a session:","metadata":{}},{"cell_type":"code","source":"print(f\"Percent of sessions where fullscreen is the same for all events: {train.groupby('session_id')['fullscreen'].nunique().eq(1).sum()/train['session_id'].nunique()*100}%\")\nprint(f\"Percent of sessions where hq is the same for all events: {train.groupby('session_id')['hq'].nunique().eq(1).sum()/train['session_id'].nunique()*100}%\")\nprint(f\"Percent of sessions where music is the same for all events: {train.groupby('session_id')['music'].nunique().eq(1).sum()/train['session_id'].nunique()*100}%\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:24:08.193950Z","iopub.execute_input":"2023-03-27T16:24:08.194815Z","iopub.status.idle":"2023-03-27T16:24:12.126663Z","shell.execute_reply.started":"2023-03-27T16:24:08.194779Z","shell.execute_reply":"2023-03-27T16:24:12.125741Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"So, it makes more sense to look at frequency per session:","metadata":{}},{"cell_type":"code","source":"gc.collect()\nplt.figure(figsize=(20, 14))\nplt.subplot(1, 3, 1)\nplt.pie(train.groupby('session_id')['fullscreen'].median().value_counts().sort_index(), \n        labels = ['No (0)', 'Yes (1)'], colors = ['pink', 'lightgreen'], autopct='%.1f%%')\nplt.title('fullscreen, % of sessions', fontsize=18)\n\nplt.subplot(1, 3, 2)\nplt.pie(train.groupby('session_id')['hq'].median().value_counts().sort_index(), \n        labels = ['No (0)', 'Yes (1)'], colors = ['pink', 'lightgreen'], autopct='%.1f%%')\nplt.title('high-quality resolution (hq), % of sessions', fontsize=18)\n\nplt.subplot(1, 3, 3)\nplt.pie(train.groupby('session_id')['music'].median().value_counts().sort_index(), \n        labels = ['No (0)', 'Yes (1)'], colors = ['pink', 'lightgreen'], autopct='%.1f%%')\nplt.title('music, % of sessions', fontsize=18)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-03-27T16:24:12.127902Z","iopub.execute_input":"2023-03-27T16:24:12.128418Z","iopub.status.idle":"2023-03-27T16:24:15.520729Z","shell.execute_reply.started":"2023-03-27T16:24:12.128385Z","shell.execute_reply":"2023-03-27T16:24:15.519338Z"},"trusted":true},"execution_count":null,"outputs":[]}]}