{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":59093,"databundleVersionId":7469972,"sourceType":"competition"},{"sourceId":7392733,"sourceType":"datasetVersion","datasetId":4297749}],"dockerImageVersionId":30646,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Purpose\nThe main purpose of this notebook is to explore and display shifts in votes and switches in evaluators over spectograms.\nThe most relevant finding is that many spectograms with hundreds of subsections have no shifts in votes. \n\nThere are additional sections mostly relating to this central plot and an animation of votes over time too.\nThe findings are discussed in more detail [here](https://www.kaggle.com/competitions/hms-harmful-brain-activity-classification/discussion/477893).","metadata":{}},{"cell_type":"markdown","source":"# Setup","metadata":{}},{"cell_type":"markdown","source":"## Import Libraries","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\n\nimport sys\nsys.path.append('/kaggle/input/kaggle-kl-div')\nfrom kaggle_kl_div import score\nimport numpy as np\n\nimport matplotlib.animation as animation\nfrom IPython.display import HTML","metadata":{"execution":{"iopub.status.busy":"2024-02-18T15:25:46.925441Z","iopub.execute_input":"2024-02-18T15:25:46.926047Z","iopub.status.idle":"2024-02-18T15:25:47.464383Z","shell.execute_reply.started":"2024-02-18T15:25:46.926000Z","shell.execute_reply":"2024-02-18T15:25:47.463230Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Load train.csv","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/hms-harmful-brain-activity-classification/train.csv')\n\n# Make names shorter\ndf.columns = df.columns.str.replace('_vote', '')\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2024-02-18T15:25:47.466412Z","iopub.execute_input":"2024-02-18T15:25:47.466919Z","iopub.status.idle":"2024-02-18T15:25:47.818412Z","shell.execute_reply.started":"2024-02-18T15:25:47.466885Z","shell.execute_reply":"2024-02-18T15:25:47.817347Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Spectograms by n_subsections","metadata":{}},{"cell_type":"code","source":"grouped_df = df.groupby('spectrogram_id').count()\nsorted_samples = grouped_df.spectrogram_sub_id.sort_values(ascending=False)\nsorted_samples[:3]","metadata":{"execution":{"iopub.status.busy":"2024-02-18T15:25:49.997335Z","iopub.execute_input":"2024-02-18T15:25:49.997789Z","iopub.status.idle":"2024-02-18T15:25:50.046273Z","shell.execute_reply.started":"2024-02-18T15:25:49.997748Z","shell.execute_reply":"2024-02-18T15:25:50.044970Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# EDA\n## Spectogram Subsections\nHere we show the 10 spectograms with the highest number of subsections.\nWhat is displayed are 4 measures in one plot:\n\n1. the total number of shifts in votes over the spectogram_subsection.\n2. the total number switches of the number of experts over the spectogram_subsection (this must at least be as high as shifts of course, as every switch is a shift).\n3. the current number of experts for this subsection.\n4. the KL divergence between the first subsection and the current.\n\n=> You can find many takeaways, but a big one is that some spectograms have hundreds of shifts and switches and some none, even though they have 800+ subsections.\n","metadata":{}},{"cell_type":"code","source":"start = 0 # Start at the most populous spectogram\nshow = 5 # Show the start and the next 5 popular spectograms","metadata":{"execution":{"iopub.status.busy":"2024-02-18T15:25:50.906615Z","iopub.execute_input":"2024-02-18T15:25:50.907851Z","iopub.status.idle":"2024-02-18T15:25:50.913158Z","shell.execute_reply.started":"2024-02-18T15:25:50.907806Z","shell.execute_reply":"2024-02-18T15:25:50.911822Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for spectogram in range(start,start+show):\n    # Initialize variables\n    shift_list = []\n    switch_list = []\n    KL_list = []\n    nvote_list = []\n\n    counter_pred_shift, counter_expert_switch = 0, 0  # Initialize counters, one for number of prediction changes, one for number of rater changes\n\n    for i, row in df[df.spectrogram_id == sorted_samples.index[spectogram]].iterrows():\n        votes = row[9:15]\n        nvote_list.append(votes.sum())\n\n        # Normalize votes so they sum up to 1, required for KL divergence\n        norm_votes = votes / votes.sum()\n        \n        if counter_pred_shift == 0:\n            df_base_vote = pd.DataFrame({\n                'id': [0],  \n                'seizure_vote': [norm_votes.iloc[0]],\n                'lpd_vote': [norm_votes.iloc[1]],\n                'gpd_vote': [norm_votes.iloc[2]],\n                'lrda_vote': [norm_votes.iloc[3]],\n                'grda_vote': [norm_votes.iloc[4]],\n                'other_vote': [norm_votes.iloc[5]]\n            })\n            counter_pred_shift += 1\n            counter_expert_switch += 1\n\n            # Calculate KL divergence for the change\n            df_norm_vote = pd.DataFrame({\n                'id': [0],  \n                'seizure_vote': [norm_votes.iloc[0]],\n                'lpd_vote': [norm_votes.iloc[1]],\n                'gpd_vote': [norm_votes.iloc[2]],\n                'lrda_vote': [norm_votes.iloc[3]],\n                'grda_vote': [norm_votes.iloc[4]],\n                'other_vote': [norm_votes.iloc[5]]\n            })\n            current_KL = score(df_base_vote.copy(), df_norm_vote, 'id', epsilon=1e-2)\n    \n        else:\n            # Compare current votes with previous to detect changes\n            if not votes.equals(prev_votes):\n                counter_pred_shift += 1\n                if votes.sum() != prev_votes.sum():\n                    counter_expert_switch += 1\n                \n                # Calculate KL divergence for the change\n                df_norm_vote = pd.DataFrame({\n                    'id': [0],  \n                    'seizure_vote': [norm_votes.iloc[0]],\n                    'lpd_vote': [norm_votes.iloc[1]],\n                    'gpd_vote': [norm_votes.iloc[2]],\n                    'lrda_vote': [norm_votes.iloc[3]],\n                    'grda_vote': [norm_votes.iloc[4]],\n                    'other_vote': [norm_votes.iloc[5]]\n                })\n                current_KL = score(df_base_vote.copy(), df_norm_vote, 'id', epsilon=1e-2)\n\n        KL_list.append(current_KL)\n        shift_list.append(counter_pred_shift)\n        switch_list.append(counter_expert_switch)\n        \n        prev_votes = votes\n\n    # Plot the shifts, switches and KL divergence\n    fig, ax1 = plt.subplots()\n\n    ax1.plot(range(sorted_samples.values[spectogram]), shift_list, label='Sum Shifts')\n    ax1.plot(range(sorted_samples.values[spectogram]), switch_list, label='Sum Switches', color='orange')\n    ax1.plot(range(sorted_samples.values[spectogram]), nvote_list, label='Num Experts', color='red', alpha=0.5)\n\n    ax1.set_xlabel('Spectrogram Index')\n    ax1.set_ylabel('Count')\n    ax2 = ax1.twinx()\n    ax2.bar(range(sorted_samples.values[spectogram]), KL_list, label='KL vs start', color='green', alpha=0.5)\n    ax2.set_ylabel('KL Divergence')\n\n    fig.legend()\n    plt.title('Spectrogram ID: ' + str(sorted_samples.index[spectogram]))\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2024-02-18T15:25:51.277381Z","iopub.execute_input":"2024-02-18T15:25:51.277904Z","iopub.status.idle":"2024-02-18T15:26:11.397193Z","shell.execute_reply.started":"2024-02-18T15:25:51.277860Z","shell.execute_reply":"2024-02-18T15:26:11.395811Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Animation\nDisplays animation of vote fractions over time for one spectogram. \n\nYou might want to decrease speed of animation.\n\nIt also runs for a few minutes for the spectograms with most subsections.","metadata":{}},{"cell_type":"code","source":"def update(frame):\n    ax.clear()\n    votes = spec.iloc[frame][9:15]\n    total_votes = votes.sum()\n    votes_percentage = votes / total_votes\n    votes_percentage.plot(kind='bar', ax=ax)\n    ax.set_xlabel('Vote')\n    ax.set_ylabel('Vote Fraction')\n    ax.set_title('Votes')\n    ax.set_ylim(0, 1)","metadata":{"execution":{"iopub.status.busy":"2024-02-18T15:26:50.404870Z","iopub.execute_input":"2024-02-18T15:26:50.405301Z","iopub.status.idle":"2024-02-18T15:26:50.412980Z","shell.execute_reply.started":"2024-02-18T15:26:50.405269Z","shell.execute_reply":"2024-02-18T15:26:50.411622Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"x_highest_spectogram = 0\n\ncounter_pred_shift, counter_rater_switch = 0, 0  # Initialize counters, one for number of prediction changes, one for number of rater changes\nspec = df[df.spectrogram_id == sorted_samples.index[x_highest_spectogram]]\n\nfig, ax = plt.subplots()\nani = animation.FuncAnimation(fig, update, frames=spec.__len__(), interval=1, repeat=False)\n\nHTML(ani.to_jshtml())","metadata":{"execution":{"iopub.status.busy":"2024-02-18T15:26:54.048120Z","iopub.execute_input":"2024-02-18T15:26:54.049104Z","iopub.status.idle":"2024-02-18T15:27:00.007269Z","shell.execute_reply.started":"2024-02-18T15:26:54.049046Z","shell.execute_reply":"2024-02-18T15:27:00.006384Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Spectogram Average\nShows average KL divergence between the first subsection and subsequent ones for the top 80 spectograms (and up to subsection 100).","metadata":{}},{"cell_type":"code","source":"KL_list_all_spect = []\nfor spectogram in range(80):\n\n    KL_list = []\n\n    first = True\n    for i, row in df[df.spectrogram_id == sorted_samples.index[spectogram]].iterrows():\n        votes = row[9:15]\n        \n        # Normalize votes so they sum up to 1, required for KL divergence\n        norm_votes = votes / votes.sum()\n        \n        if first:\n            first = False\n            df_base_vote = pd.DataFrame({\n                'id': [0],  \n                'seizure_vote': [norm_votes.iloc[0]],\n                'lpd_vote': [norm_votes.iloc[1]],\n                'gpd_vote': [norm_votes.iloc[2]],\n                'lrda_vote': [norm_votes.iloc[3]],\n                'grda_vote': [norm_votes.iloc[4]],\n                'other_vote': [norm_votes.iloc[5]]\n            })\n\n            # Calculate KL divergence for the change\n            df_norm_vote = pd.DataFrame({\n                'id': [0],  \n                'seizure_vote': [norm_votes.iloc[0]],\n                'lpd_vote': [norm_votes.iloc[1]],\n                'gpd_vote': [norm_votes.iloc[2]],\n                'lrda_vote': [norm_votes.iloc[3]],\n                'grda_vote': [norm_votes.iloc[4]],\n                'other_vote': [norm_votes.iloc[5]]\n            })\n            current_KL = score(df_base_vote.copy(), df_norm_vote, 'id', epsilon=1e-2)\n    \n        else:\n            # Compare current votes with previous to detect changes\n            if not votes.equals(prev_votes):\n                counter_pred_shift += 1\n                if votes.sum() != prev_votes.sum():\n                    counter_rater_switch += 1\n                \n                # Calculate KL divergence for the change\n                df_norm_vote = pd.DataFrame({\n                    'id': [0],  \n                    'seizure_vote': [norm_votes.iloc[0]],\n                    'lpd_vote': [norm_votes.iloc[1]],\n                    'gpd_vote': [norm_votes.iloc[2]],\n                    'lrda_vote': [norm_votes.iloc[3]],\n                    'grda_vote': [norm_votes.iloc[4]],\n                    'other_vote': [norm_votes.iloc[5]]\n                })\n                current_KL = score(df_base_vote.copy(), df_norm_vote, 'id', epsilon=1e-2)\n\n        KL_list.append(current_KL)\n        prev_votes = votes\n\n    KL_list_all_spect.append(KL_list[:100])\n\nKL_array = np.array(KL_list_all_spect)\nplt.plot(list(KL_array.mean(axis=0)))\nplt.xlabel('Index')\nplt.ylabel('Mean KL')\nplt.title('Mean KL vs Index')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-02-18T10:23:18.467245Z","iopub.execute_input":"2024-02-18T10:23:18.467666Z","iopub.status.idle":"2024-02-18T10:24:13.545942Z","shell.execute_reply.started":"2024-02-18T10:23:18.467630Z","shell.execute_reply":"2024-02-18T10:24:13.544736Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Display Particular Spectogram","metadata":{}},{"cell_type":"code","source":"def display_all(df):\n    with pd.option_context(\"display.max_rows\", 1000, \"display.max_columns\", 1000): \n        display(df)","metadata":{"execution":{"iopub.status.busy":"2024-02-18T10:24:13.547866Z","iopub.execute_input":"2024-02-18T10:24:13.548269Z","iopub.status.idle":"2024-02-18T10:24:13.555007Z","shell.execute_reply.started":"2024-02-18T10:24:13.548238Z","shell.execute_reply":"2024-02-18T10:24:13.553704Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"display_all(df[df.spectrogram_id == 1266022743])","metadata":{"execution":{"iopub.status.busy":"2024-02-18T10:24:13.558196Z","iopub.execute_input":"2024-02-18T10:24:13.558603Z","iopub.status.idle":"2024-02-18T10:24:13.593405Z","shell.execute_reply.started":"2024-02-18T10:24:13.558570Z","shell.execute_reply":"2024-02-18T10:24:13.592280Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Offset Between Subsections","metadata":{}},{"cell_type":"code","source":"df['eeg_combined'] = df.eeg_id * 100 + df.eeg_sub_id\ndf['spec_combined'] = df.spectrogram_id * 100 + df.spectrogram_sub_id\n\nvalue_counts = df.spectrogram_label_offset_seconds.value_counts()\n\n# Create a new dataframe with the desired ranges\nranges_df = pd.DataFrame({'Range': ['0-10', '10-20', '20-30', '30-40', '40-50', '50-60', '60-70', '70-80', '80-90', '90-100', '100-200', '200-300', '300-400', '400-500', '500-1000', '1000+'],\n                          'Count': [value_counts[(value_counts.index >= 0) & (value_counts.index < 10)].sum(),\n                                    value_counts[(value_counts.index >= 10) & (value_counts.index < 20)].sum(),\n                                    value_counts[(value_counts.index >= 20) & (value_counts.index < 30)].sum(),\n                                    value_counts[(value_counts.index >= 30) & (value_counts.index < 40)].sum(),\n                                    value_counts[(value_counts.index >= 40) & (value_counts.index < 50)].sum(),\n                                    value_counts[(value_counts.index >= 50) & (value_counts.index < 60)].sum(),\n                                    value_counts[(value_counts.index >= 60) & (value_counts.index < 70)].sum(),\n                                    value_counts[(value_counts.index >= 70) & (value_counts.index < 80)].sum(),\n                                    value_counts[(value_counts.index >= 80) & (value_counts.index < 90)].sum(),\n                                    value_counts[(value_counts.index >= 90) & (value_counts.index < 100)].sum(),\n                                    value_counts[(value_counts.index >= 100) & (value_counts.index < 200)].sum(),\n                                    value_counts[(value_counts.index >= 200) & (value_counts.index < 300)].sum(),\n                                    value_counts[(value_counts.index >= 300) & (value_counts.index < 400)].sum(),\n                                    value_counts[(value_counts.index >= 400) & (value_counts.index < 500)].sum(),\n                                    value_counts[(value_counts.index >= 500) & (value_counts.index < 1000)].sum(),\n                                    value_counts[(value_counts.index >= 1000)].sum()]})\n\n# Plot the new dataframe\nranges_df.plot(x='Range', y='Count', kind='bar')\n\nplt.xticks(range(len(ranges_df)), ranges_df['Range'], rotation=45)\nplt.ylabel('Count')\nplt.title('Value Counts')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-02-18T10:24:13.595038Z","iopub.execute_input":"2024-02-18T10:24:13.595379Z","iopub.status.idle":"2024-02-18T10:24:13.916906Z","shell.execute_reply.started":"2024-02-18T10:24:13.595349Z","shell.execute_reply":"2024-02-18T10:24:13.915785Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}