{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.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":59156,"databundleVersionId":13874099,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:11.108081Z","iopub.execute_input":"2025-10-14T05:22:11.108803Z","iopub.status.idle":"2025-10-14T05:22:11.665488Z","shell.execute_reply.started":"2025-10-14T05:22:11.108743Z","shell.execute_reply":"2025-10-14T05:22:11.664247Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# MABe — Dataset EDA\n\nExploration of metadata, tracking (.parquet), and annotation labels for the Kaggle **MABe: Mouse Behavior Detection** competition.\n\n**Folder assumption**: data is under `/kaggle/input/MABe-mouse-behavior-detection` relative to this notebook.\n","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport os\nfrom pathlib import Path\n\n# Matplotlib defaults\nplt.rcParams[\"figure.figsize\"] = (8, 4)\nplt.rcParams[\"axes.grid\"] = True\n\nDATA_DIR = Path(\"/kaggle/input/MABe-mouse-behavior-detection\") \nprint(\"Using DATA_DIR:\", DATA_DIR.resolve())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:11.667058Z","iopub.execute_input":"2025-10-14T05:22:11.667897Z","iopub.status.idle":"2025-10-14T05:22:11.676971Z","shell.execute_reply.started":"2025-10-14T05:22:11.667868Z","shell.execute_reply":"2025-10-14T05:22:11.675861Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1) Load Train/Test Metadata CSVs","metadata":{}},{"cell_type":"code","source":"train_meta_path = DATA_DIR / \"train.csv\"\ntest_meta_path  = DATA_DIR / \"test.csv\"\nassert train_meta_path.exists(), f\"Missing {train_meta_path}\"\nassert test_meta_path.exists(),  f\"Missing {test_meta_path}\"\n\ntrain_meta = pd.read_csv(train_meta_path)\ntest_meta  = pd.read_csv(test_meta_path)\nprint(\"train_meta shape:\", train_meta.shape)\ndisplay(train_meta.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:11.677898Z","iopub.execute_input":"2025-10-14T05:22:11.678156Z","iopub.status.idle":"2025-10-14T05:22:11.922087Z","shell.execute_reply.started":"2025-10-14T05:22:11.678131Z","shell.execute_reply":"2025-10-14T05:22:11.921134Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2) Quick Overview","metadata":{}},{"cell_type":"code","source":"n_labs = train_meta[\"lab_id\"].nunique() if \"lab_id\" in train_meta.columns else np.nan\nn_videos = train_meta[\"video_id\"].nunique() if \"video_id\" in train_meta.columns else np.nan\nprint({\"labs\": n_labs, \"videos\": n_videos})\ndisplay(train_meta.describe(include=\"all\").transpose())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:11.924284Z","iopub.execute_input":"2025-10-14T05:22:11.924568Z","iopub.status.idle":"2025-10-14T05:22:12.039649Z","shell.execute_reply.started":"2025-10-14T05:22:11.924547Z","shell.execute_reply":"2025-10-14T05:22:12.038563Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:12.041058Z","iopub.execute_input":"2025-10-14T05:22:12.041355Z","iopub.status.idle":"2025-10-14T05:22:12.070099Z","shell.execute_reply.started":"2025-10-14T05:22:12.041333Z","shell.execute_reply":"2025-10-14T05:22:12.069014Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta.isnull().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:12.071279Z","iopub.execute_input":"2025-10-14T05:22:12.072117Z","iopub.status.idle":"2025-10-14T05:22:12.092936Z","shell.execute_reply.started":"2025-10-14T05:22:12.072091Z","shell.execute_reply":"2025-10-14T05:22:12.091728Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3) Video Stats (FPS / Duration)","metadata":{}},{"cell_type":"code","source":"fps_col = \"frames_per_second\"\ndur_col = \"video_duration_sec\"\n\nif fps_col in train_meta.columns:\n    train_meta[fps_col].dropna().astype(float).hist(bins=30)\n    plt.title(\"Distribution of Frame Rates (FPS)\")\n    plt.xlabel(\"Frames per second\")\n    plt.ylabel(\"Count\")\n    plt.show()\n\nif dur_col in train_meta.columns:\n    train_meta[dur_col].dropna().astype(float).hist(bins=30)\n    plt.title(\"Video Duration Distribution\")\n    plt.xlabel(\"Seconds\")\n    plt.ylabel(\"Count\")\n    plt.show()\n\nprint(\"FPS describe:\\n\", train_meta.get(fps_col, pd.Series(dtype=float)).describe())\nprint(\"Duration describe:\\n\", train_meta.get(dur_col, pd.Series(dtype=float)).describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:12.094019Z","iopub.execute_input":"2025-10-14T05:22:12.094330Z","iopub.status.idle":"2025-10-14T05:22:12.754172Z","shell.execute_reply.started":"2025-10-14T05:22:12.094306Z","shell.execute_reply":"2025-10-14T05:22:12.753048Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4) Arena & Setup","metadata":{}},{"cell_type":"code","source":"arena_shape_col = \"arena_shape\"\narena_type_col = \"arena_type\"\n\nif arena_shape_col in train_meta.columns:\n    vc = train_meta[arena_shape_col].value_counts(dropna=False)\n    fig, ax = plt.subplots(figsize=(8, 4))\n    bars = ax.bar(vc.index.astype(str), vc.values, color=\"skyblue\", edgecolor=\"black\")\n    ax.bar_label(ax.containers[0])\n    ax.set_title(\"Arena Shape Distribution\")\n    ax.set_xlabel(\"Arena Shape\")\n    ax.set_ylabel(\"Count\")\n    plt.xticks(rotation=45, ha=\"right\")\n    plt.tight_layout()\n    plt.show()\n\nif arena_type_col in train_meta.columns:\n    vc = train_meta[arena_type_col].value_counts(dropna=False)\n    fig, ax = plt.subplots(figsize=(8, 4))\n    bars = ax.bar(vc.index.astype(str), vc.values, color=\"skyblue\", edgecolor=\"black\")\n    ax.bar_label(ax.containers[0])\n    ax.set_title(\"Arena Type Distribution\")\n    ax.set_xlabel(\"Arena Type\")\n    ax.set_ylabel(\"Count\")\n    plt.xticks(rotation=45, ha=\"right\")\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:12.755169Z","iopub.execute_input":"2025-10-14T05:22:12.755492Z","iopub.status.idle":"2025-10-14T05:22:13.270363Z","shell.execute_reply.started":"2025-10-14T05:22:12.755467Z","shell.execute_reply":"2025-10-14T05:22:13.269268Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5) Analyze mouse<n>_condition Columns","metadata":{}},{"cell_type":"code","source":"train_meta.mouse1_condition.nunique(), train_meta.mouse1_condition.unique()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:13.271366Z","iopub.execute_input":"2025-10-14T05:22:13.271638Z","iopub.status.idle":"2025-10-14T05:22:13.281026Z","shell.execute_reply.started":"2025-10-14T05:22:13.271616Z","shell.execute_reply":"2025-10-14T05:22:13.279914Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta.mouse2_condition.nunique(), train_meta.mouse3_condition.nunique(),train_meta.mouse4_condition.nunique()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:13.284809Z","iopub.execute_input":"2025-10-14T05:22:13.285069Z","iopub.status.idle":"2025-10-14T05:22:13.311669Z","shell.execute_reply.started":"2025-10-14T05:22:13.285051Z","shell.execute_reply":"2025-10-14T05:22:13.310594Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta.mouse4_condition.unique()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:13.312915Z","iopub.execute_input":"2025-10-14T05:22:13.313150Z","iopub.status.idle":"2025-10-14T05:22:13.335790Z","shell.execute_reply.started":"2025-10-14T05:22:13.313130Z","shell.execute_reply":"2025-10-14T05:22:13.334561Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta.mouse1_condition.unique()[200:250]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:13.336920Z","iopub.execute_input":"2025-10-14T05:22:13.337248Z","iopub.status.idle":"2025-10-14T05:22:13.362594Z","shell.execute_reply.started":"2025-10-14T05:22:13.337226Z","shell.execute_reply":"2025-10-14T05:22:13.361092Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# We will analize 10 top conditions\n\n# Identify all mouse<n>_condition columns automatically\ncond_cols = [c for c in train_meta.columns if c.startswith(\"mouse\") and c.endswith(\"_condition\")]\n\nprint(f\"Found {len(cond_cols)} condition columns:\", cond_cols)\n\nsummary_list = []\n\nfor col in cond_cols:\n    vc = train_meta[col].value_counts(dropna=False)\n    print(f\"\\n=== {col} ===\")\n    print(vc.head(10))  # top 10 values\n    print(f\"Unique: {vc.index.nunique()}, Missing: {train_meta[col].isna().sum()}\")\n\n    # save for combined summary\n    summary_list.append(\n        pd.DataFrame({\n            \"column\": col,\n            \"condition\": vc.index.astype(str),\n            \"count\": vc.values\n        })\n    )\n\n# Combine all results\ndf_conditions = pd.concat(summary_list, ignore_index=True)\n\n# Clean up (remove weird spacing or quotes)\ndf_conditions[\"condition\"] = df_conditions[\"condition\"].str.strip().str.strip(\"'\\\"\").replace({\"nan\": \"Missing\"})\n\n# Sort by frequency\ntop_conditions = (\n    df_conditions.groupby(\"condition\")[\"count\"]\n    .sum()\n    .sort_values(ascending=False)\n    .reset_index()\n)\n\nprint(\"\\nTop overall conditions across all mice:\")\nprint(top_conditions.head(10))\n\n# Visualization\nplt.figure(figsize=(8,5))\nbars = plt.bar(top_conditions[\"condition\"].head(10), top_conditions[\"count\"].head(10), color=\"skyblue\")\nplt.title(\"Top 10 Mouse Conditions (All Columns Combined)\")\nplt.xticks(rotation=45, ha=\"right\")\nplt.ylabel(\"Count\")\nplt.tight_layout()\nplt.bar_label(bars, fmt=\"%d\", label_type=\"edge\", padding=3)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:13.364076Z","iopub.execute_input":"2025-10-14T05:22:13.364341Z","iopub.status.idle":"2025-10-14T05:22:13.695966Z","shell.execute_reply.started":"2025-10-14T05:22:13.364320Z","shell.execute_reply":"2025-10-14T05:22:13.694655Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6) Labeled Behaviors(from metadata)|","metadata":{}},{"cell_type":"code","source":"train_meta.head(7).behaviors_labeled","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:13.697098Z","iopub.execute_input":"2025-10-14T05:22:13.697417Z","iopub.status.idle":"2025-10-14T05:22:13.705618Z","shell.execute_reply.started":"2025-10-14T05:22:13.697394Z","shell.execute_reply":"2025-10-14T05:22:13.704802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta.behaviors_labeled[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:13.706526Z","iopub.execute_input":"2025-10-14T05:22:13.706818Z","iopub.status.idle":"2025-10-14T05:22:13.728213Z","shell.execute_reply.started":"2025-10-14T05:22:13.706794Z","shell.execute_reply":"2025-10-14T05:22:13.727183Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta.iloc[0,:]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:13.729110Z","iopub.execute_input":"2025-10-14T05:22:13.729329Z","iopub.status.idle":"2025-10-14T05:22:13.755010Z","shell.execute_reply.started":"2025-10-14T05:22:13.729312Z","shell.execute_reply":"2025-10-14T05:22:13.754140Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Parse `behaviors labeled` into tidy rows","metadata":{}},{"cell_type":"code","source":"import ast","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:13.756283Z","iopub.execute_input":"2025-10-14T05:22:13.757012Z","iopub.status.idle":"2025-10-14T05:22:13.778822Z","shell.execute_reply.started":"2025-10-14T05:22:13.756980Z","shell.execute_reply":"2025-10-14T05:22:13.777457Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"col = \"behaviors_labeled\"\nvid_col = \"video_id\"\n\nrecords = []\nskipped = 0\nfor i, row in train_meta.iterrows():\n    s = row.get(col, None)\n    vid = row.get(vid_col, None)\n    if pd.isna(s):\n        continue\n    try:\n        items = ast.literal_eval(s)\n        if not isinstance(items, (list, tuple)):\n            skipped += 1\n            continue\n        for it in items:\n            parts = str(it).split(\",\")\n            if len(parts) != 3:\n                continue\n            agent, target, behavior = [p.strip().strip(\"'\\\"\") for p in parts]\n            records.append({\"video_id\": vid, \"agent\": agent, \"target\": target, \"behavior\": behavior})\n    except Exception:\n        skipped += 1\n\ndf_beh = pd.DataFrame.from_records(records)\nprint(\"Parsed rows:\", len(df_beh), \"| Skipped rows:\", skipped)\ndisplay(df_beh.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:13.780188Z","iopub.execute_input":"2025-10-14T05:22:13.781384Z","iopub.status.idle":"2025-10-14T05:22:14.274893Z","shell.execute_reply.started":"2025-10-14T05:22:13.781345Z","shell.execute_reply":"2025-10-14T05:22:14.273903Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta.video_id.nunique(), df_beh.video_id.nunique()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:14.276002Z","iopub.execute_input":"2025-10-14T05:22:14.276387Z","iopub.status.idle":"2025-10-14T05:22:14.284307Z","shell.execute_reply.started":"2025-10-14T05:22:14.276366Z","shell.execute_reply":"2025-10-14T05:22:14.283219Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta.video_id.nunique()-train_meta.video_id.isna().sum()-train_meta.behaviors_labeled.isna().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:14.285249Z","iopub.execute_input":"2025-10-14T05:22:14.285578Z","iopub.status.idle":"2025-10-14T05:22:14.309548Z","shell.execute_reply.started":"2025-10-14T05:22:14.285558Z","shell.execute_reply":"2025-10-14T05:22:14.308555Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_beh.target.isna().sum(), df_beh.agent.isna().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:14.310500Z","iopub.execute_input":"2025-10-14T05:22:14.311206Z","iopub.status.idle":"2025-10-14T05:22:14.330845Z","shell.execute_reply.started":"2025-10-14T05:22:14.311180Z","shell.execute_reply":"2025-10-14T05:22:14.329375Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_beh.agent.unique(), df_beh.target.unique()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:14.332315Z","iopub.execute_input":"2025-10-14T05:22:14.332689Z","iopub.status.idle":"2025-10-14T05:22:14.357598Z","shell.execute_reply.started":"2025-10-14T05:22:14.332616Z","shell.execute_reply":"2025-10-14T05:22:14.356010Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_beh.groupby('video_id')['target'].unique().apply(list).reset_index(name=\"behaviors_list\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:14.358921Z","iopub.execute_input":"2025-10-14T05:22:14.359253Z","iopub.status.idle":"2025-10-14T05:22:14.428649Z","shell.execute_reply.started":"2025-10-14T05:22:14.359232Z","shell.execute_reply":"2025-10-14T05:22:14.427572Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_beh.groupby('video_id')['agent'].unique().apply(list).reset_index(name=\"behaviors_list\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:14.429648Z","iopub.execute_input":"2025-10-14T05:22:14.430010Z","iopub.status.idle":"2025-10-14T05:22:14.491702Z","shell.execute_reply.started":"2025-10-14T05:22:14.429987Z","shell.execute_reply":"2025-10-14T05:22:14.490280Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Self vs Social split","metadata":{}},{"cell_type":"code","source":"df_beh[\"is_self\"] = (df_beh[\"target\"].str.lower() == \"self\") | (df_beh[\"agent\"].str.lower() == df_beh[\"target\"].str.lower())\nsplit_counts = df_beh[\"is_self\"].map({True: \"self\", False: \"social\"}).value_counts()\ndisplay(split_counts.to_frame(\"count\"))\n\nfig, ax = plt.subplots(figsize=(6, 4))\nbars = ax.bar(split_counts.index.astype(str), split_counts.values)\nax.set_title(\"Self vs Social (All Videos)\")\nax.set_xlabel(\"Type\")\nax.set_ylabel(\"Count\")\nax.bar_label(ax.containers[0], fmt='%d', label_type='edge', padding=3)\nfig.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:14.492641Z","iopub.execute_input":"2025-10-14T05:22:14.492921Z","iopub.status.idle":"2025-10-14T05:22:14.683193Z","shell.execute_reply.started":"2025-10-14T05:22:14.492901Z","shell.execute_reply":"2025-10-14T05:22:14.682055Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Agent→Target pair counts","metadata":{}},{"cell_type":"code","source":"pair_counts = df_beh.groupby([\"agent\", \"target\"]).size()\ndisplay(pair_counts.to_frame(\"count\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:14.684344Z","iopub.execute_input":"2025-10-14T05:22:14.684703Z","iopub.status.idle":"2025-10-14T05:22:14.700894Z","shell.execute_reply.started":"2025-10-14T05:22:14.684672Z","shell.execute_reply":"2025-10-14T05:22:14.699865Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#11+17+276+290","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:14.701691Z","iopub.execute_input":"2025-10-14T05:22:14.701993Z","iopub.status.idle":"2025-10-14T05:22:14.718303Z","shell.execute_reply.started":"2025-10-14T05:22:14.701973Z","shell.execute_reply":"2025-10-14T05:22:14.717195Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels = [f\"{a}→{t}\" for a,t in pair_counts.index]\nfig, ax = plt.subplots(figsize=(8,4))\nbars = ax.bar(labels, pair_counts.values)\nax.set_title(\"Top Agent→Target Pairs\")\nax.set_xlabel(\"Pair\")\nax.set_ylabel(\"Count\")\nplt.xticks(rotation=45, ha=\"right\")\nax.bar_label(ax.containers[0], fmt='%d', label_type='edge', padding=3)\nfig.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:14.724869Z","iopub.execute_input":"2025-10-14T05:22:14.725159Z","iopub.status.idle":"2025-10-14T05:22:15.098124Z","shell.execute_reply.started":"2025-10-14T05:22:14.725141Z","shell.execute_reply":"2025-10-14T05:22:15.096865Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Behavior counts (overall)","metadata":{}},{"cell_type":"code","source":"beh_counts = df_beh[\"behavior\"].value_counts()\nprint(f\"Number of unique behaviors is {df_beh.behavior.nunique()}\")\ndisplay(beh_counts.to_frame(\"count\"))\n\nfig, ax = plt.subplots(figsize=(8, 5))\nbars = ax.bar(beh_counts.index.astype(str), beh_counts.values)\nax.set_title(\"Behavior Frequency (All Videos)\")\nax.set_xlabel(\"Behavior\")\nax.set_ylabel(\"Count\")\nplt.xticks(rotation=45, ha=\"right\")\nfig.tight_layout()\nax.bar_label(ax.containers[0], fmt='%d', label_type='edge', padding=3)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:15.099229Z","iopub.execute_input":"2025-10-14T05:22:15.099554Z","iopub.status.idle":"2025-10-14T05:22:15.700703Z","shell.execute_reply.started":"2025-10-14T05:22:15.099528Z","shell.execute_reply":"2025-10-14T05:22:15.699121Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6) Tracking Files (train_tracking/*.parquet)\n\nLoad one sample tracking file, inspect columns, and plot a single bodypart trajectory.","metadata":{}},{"cell_type":"code","source":"train_meta.lab_id.value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:15.702147Z","iopub.execute_input":"2025-10-14T05:22:15.702608Z","iopub.status.idle":"2025-10-14T05:22:15.713244Z","shell.execute_reply.started":"2025-10-14T05:22:15.702573Z","shell.execute_reply":"2025-10-14T05:22:15.712110Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:15.714705Z","iopub.execute_input":"2025-10-14T05:22:15.715075Z","iopub.status.idle":"2025-10-14T05:22:15.757163Z","shell.execute_reply.started":"2025-10-14T05:22:15.715044Z","shell.execute_reply":"2025-10-14T05:22:15.754931Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"lab_id_exp = \"AdaptableSnail\"\nvideo_id_exp = 44566106","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:15.758295Z","iopub.execute_input":"2025-10-14T05:22:15.758628Z","iopub.status.idle":"2025-10-14T05:22:15.775917Z","shell.execute_reply.started":"2025-10-14T05:22:15.758601Z","shell.execute_reply":"2025-10-14T05:22:15.774842Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_meta[(train_meta.lab_id == lab_id_exp) & (train_meta.video_id == video_id_exp)]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:15.777190Z","iopub.execute_input":"2025-10-14T05:22:15.777686Z","iopub.status.idle":"2025-10-14T05:22:15.816221Z","shell.execute_reply.started":"2025-10-14T05:22:15.777652Z","shell.execute_reply":"2025-10-14T05:22:15.815353Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_beh[df_beh.video_id == video_id_exp].groupby([\"agent\", \"target\"]).size()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:15.817835Z","iopub.execute_input":"2025-10-14T05:22:15.818159Z","iopub.status.idle":"2025-10-14T05:22:15.847493Z","shell.execute_reply.started":"2025-10-14T05:22:15.818126Z","shell.execute_reply":"2025-10-14T05:22:15.846374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_beh[df_beh.video_id == video_id_exp]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:15.848477Z","iopub.execute_input":"2025-10-14T05:22:15.848741Z","iopub.status.idle":"2025-10-14T05:22:15.879619Z","shell.execute_reply.started":"2025-10-14T05:22:15.848721Z","shell.execute_reply":"2025-10-14T05:22:15.878567Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"track_dir = DATA_DIR /'train_tracking'/lab_id_exp\nsample_track = None\nif track_dir.exists():\n    # try to find a parquet named after a train video_id\n    cand = track_dir / f\"{video_id_exp}.parquet\"\n    if cand.exists():\n        sample_track = cand\n    # or just take any parquet in the folder\n    if sample_track is None:\n        pq_files = list(track_dir.glob(\"*.parquet\"))\n        if pq_files:\n            sample_track = pq_files[0]\n\nif sample_track is not None:\n    print(\"Sample tracking file:\", sample_track)\n    try:\n        df_track = pd.read_parquet(sample_track)\n    except Exception as e:\n        print(\"Parquet read error:\", e)\n        df_track = None\nelse:\n    print(\"No tracking parquet found under\", track_dir)\n    df_track = None\n\nif df_track is not None and not df_track.empty:\n    display(df_track.head())\n    cols = df_track.columns.tolist()\n    need = {\"video_frame\", \"mouse_id\", \"bodypart\", \"x\", \"y\"}\n    if need.issubset(set(cols)):\n        mouse0 = df_track[\"mouse_id\"].iloc[0]\n        body0 = df_track[\"bodypart\"].iloc[0]\n        path_df = df_track[(df_track[\"mouse_id\"] == mouse0) & (df_track[\"bodypart\"] == body0)]#.head(500)\n        plt.plot(path_df[\"x\"].values, path_df[\"y\"].values, linewidth=1)\n        plt.gca().invert_yaxis()\n        plt.title(f\"Trajectory of {body0} (Mouse {mouse0})\")\n        plt.xlabel(\"x (pixels)\")\n        plt.ylabel(\"y (pixels)\")\n        plt.show()\n    else:\n        print(\"Expected tracking columns not found. Got:\", cols)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:15.880846Z","iopub.execute_input":"2025-10-14T05:22:15.881184Z","iopub.status.idle":"2025-10-14T05:22:16.730066Z","shell.execute_reply.started":"2025-10-14T05:22:15.881146Z","shell.execute_reply":"2025-10-14T05:22:16.728613Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(df_track.shape)\ndf_track","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:16.731080Z","iopub.execute_input":"2025-10-14T05:22:16.731353Z","iopub.status.idle":"2025-10-14T05:22:16.745072Z","shell.execute_reply.started":"2025-10-14T05:22:16.731331Z","shell.execute_reply":"2025-10-14T05:22:16.744185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_track[(df_track.mouse_id == 1)&(df_track.bodypart=='body_center')]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:16.746039Z","iopub.execute_input":"2025-10-14T05:22:16.746320Z","iopub.status.idle":"2025-10-14T05:22:16.867830Z","shell.execute_reply.started":"2025-10-14T05:22:16.746299Z","shell.execute_reply":"2025-10-14T05:22:16.866803Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_track.nunique()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:16.868686Z","iopub.execute_input":"2025-10-14T05:22:16.868970Z","iopub.status.idle":"2025-10-14T05:22:17.154863Z","shell.execute_reply.started":"2025-10-14T05:22:16.868948Z","shell.execute_reply":"2025-10-14T05:22:17.153646Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_track.groupby(\"mouse_id\").size()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.156016Z","iopub.execute_input":"2025-10-14T05:22:17.156400Z","iopub.status.idle":"2025-10-14T05:22:17.274855Z","shell.execute_reply.started":"2025-10-14T05:22:17.156354Z","shell.execute_reply":"2025-10-14T05:22:17.273903Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7) Annotation Files (train_annotation/*.parquet)","metadata":{}},{"cell_type":"code","source":"annotation_dir = DATA_DIR /'train_annotation'/lab_id_exp","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.276179Z","iopub.execute_input":"2025-10-14T05:22:17.276577Z","iopub.status.idle":"2025-10-14T05:22:17.282047Z","shell.execute_reply.started":"2025-10-14T05:22:17.276548Z","shell.execute_reply":"2025-10-14T05:22:17.281078Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"annotation_data = pd.read_parquet(annotation_dir/ f\"{video_id_exp}.parquet\")\nprint(annotation_data.shape)\nannotation_data.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.282957Z","iopub.execute_input":"2025-10-14T05:22:17.283193Z","iopub.status.idle":"2025-10-14T05:22:17.325166Z","shell.execute_reply.started":"2025-10-14T05:22:17.283175Z","shell.execute_reply":"2025-10-14T05:22:17.324088Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"annotation_data.head(20)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.326355Z","iopub.execute_input":"2025-10-14T05:22:17.326701Z","iopub.status.idle":"2025-10-14T05:22:17.337600Z","shell.execute_reply.started":"2025-10-14T05:22:17.326673Z","shell.execute_reply":"2025-10-14T05:22:17.336791Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(annotation_data.agent_id.unique())\nannotation_data.agent_id.value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.338652Z","iopub.execute_input":"2025-10-14T05:22:17.338997Z","iopub.status.idle":"2025-10-14T05:22:17.365607Z","shell.execute_reply.started":"2025-10-14T05:22:17.338969Z","shell.execute_reply":"2025-10-14T05:22:17.364479Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(annotation_data.target_id.unique())\nannotation_data.target_id.value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.366663Z","iopub.execute_input":"2025-10-14T05:22:17.367188Z","iopub.status.idle":"2025-10-14T05:22:17.392964Z","shell.execute_reply.started":"2025-10-14T05:22:17.367159Z","shell.execute_reply":"2025-10-14T05:22:17.391808Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(annotation_data.action.unique())\nannotation_data.action.value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.394348Z","iopub.execute_input":"2025-10-14T05:22:17.394677Z","iopub.status.idle":"2025-10-14T05:22:17.431044Z","shell.execute_reply.started":"2025-10-14T05:22:17.394645Z","shell.execute_reply":"2025-10-14T05:22:17.429895Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"annotation_data.start_frame.min(), annotation_data.start_frame.max()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.432067Z","iopub.execute_input":"2025-10-14T05:22:17.432315Z","iopub.status.idle":"2025-10-14T05:22:17.455350Z","shell.execute_reply.started":"2025-10-14T05:22:17.432295Z","shell.execute_reply":"2025-10-14T05:22:17.454352Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"annotation_data.stop_frame.min(), annotation_data.stop_frame.max()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.456374Z","iopub.execute_input":"2025-10-14T05:22:17.456623Z","iopub.status.idle":"2025-10-14T05:22:17.481718Z","shell.execute_reply.started":"2025-10-14T05:22:17.456602Z","shell.execute_reply":"2025-10-14T05:22:17.480590Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Trajectory of moving","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\n\ndef plot_action_trajectory(track_parquet,\n                           ann_parquet,\n                           action_idx=0,\n                           bodypart=None,\n                           max_points=None):\n    \"\"\"\n    Plot agent and target trajectories for one annotated action.\n    - track_parquet: path to <video_id>.parquet with columns [video_frame, mouse_id, bodypart, x, y]\n    - ann_parquet:   path to <video_id>.parquet with columns [agent_id, target_id, action, start_frame, stop_frame]\n    - action_idx:    which row in the annotations to plot (after any external filtering)\n    - bodypart:      e.g. 'nose' or 'center'. If None, uses centroid over all bodyparts per (frame, mouse).\n    - max_points:    optionally limit number of plotted points for readability\n    \"\"\"\n    track_parquet = Path(track_parquet)\n    ann_parquet   = Path(ann_parquet)\n\n    df_track = pd.read_parquet(track_parquet)\n    df_ann   = pd.read_parquet(ann_parquet)\n\n    assert len(df_ann) > action_idx, f\"action_idx {action_idx} out of range (len={len(df_ann)})\"\n    ann = df_ann.iloc[action_idx]\n\n    agent_id = int(ann[\"agent_id\"])\n    target_id = int(ann[\"target_id\"])\n    start_f = int(ann[\"start_frame\"])\n    stop_f  = int(ann[\"stop_frame\"])\n    action  = str(ann[\"action\"])\n\n    # Slice the time window\n    mask_t = (df_track[\"video_frame\"] >= start_f) & (df_track[\"video_frame\"] <= stop_f)\n    dfw = df_track.loc[mask_t].copy()\n\n    # Harmonize dtypes\n    dfw[\"mouse_id\"] = pd.to_numeric(dfw[\"mouse_id\"], errors=\"coerce\").astype(\"Int64\")\n\n    # Either pick one bodypart, or compute centroid per (frame, mouse)\n    if bodypart is not None and bodypart in dfw[\"bodypart\"].unique():\n        dfw = dfw[dfw[\"bodypart\"] == bodypart]\n        # Keep one row per (frame, mouse)\n        dfw = dfw.sort_values([\"mouse_id\", \"video_frame\"])\n    else:\n        # centroid across available bodyparts at each (frame, mouse)\n        dfw = (\n            dfw.groupby([\"video_frame\", \"mouse_id\"], as_index=False)[[\"x\",\"y\"]]\n               .mean()\n               .sort_values([\"mouse_id\", \"video_frame\"])\n        )\n\n    # Extract trajectories\n    traj_agent  = dfw[dfw[\"mouse_id\"] == agent_id].copy()\n    traj_target = dfw[dfw[\"mouse_id\"] == target_id].copy()\n\n    if max_points is not None:\n        traj_agent  = traj_agent.iloc[::max(1, len(traj_agent)//max_points or 1)]\n        traj_target = traj_target.iloc[::max(1, len(traj_target)//max_points or 1)]\n\n    # Plot\n    fig, ax = plt.subplots(figsize=(6, 6))\n    if agent_id == target_id:\n        ax.plot(traj_agent[\"x\"], traj_agent[\"y\"], linewidth=2, label=f\"agent=target={agent_id}\")\n        if not traj_agent.empty:\n            ax.scatter(traj_agent[\"x\"].iloc[0],  traj_agent[\"y\"].iloc[0],  marker=\"o\", s=60, label=\"start\")\n            ax.scatter(traj_agent[\"x\"].iloc[-1], traj_agent[\"y\"].iloc[-1], marker=\"X\", s=80, label=\"end\")\n    else:\n        ax.plot(traj_agent[\"x\"],  traj_agent[\"y\"],  linewidth=2, label=f\"agent {agent_id}\")\n        ax.plot(traj_target[\"x\"], traj_target[\"y\"], linewidth=2, label=f\"target {target_id}\")\n        if not traj_agent.empty:\n            ax.scatter(traj_agent[\"x\"].iloc[0],  traj_agent[\"y\"].iloc[0],  marker=\"o\", s=60, label=\"agent start\")\n            ax.scatter(traj_agent[\"x\"].iloc[-1], traj_agent[\"y\"].iloc[-1], marker=\"X\", s=80, label=\"agent end\")\n        if not traj_target.empty:\n            ax.scatter(traj_target[\"x\"].iloc[0],  traj_target[\"y\"].iloc[0],  marker=\"o\", s=60, label=\"target start\")\n            ax.scatter(traj_target[\"x\"].iloc[-1], traj_target[\"y\"].iloc[-1], marker=\"X\", s=80, label=\"target end\")\n\n    # Image coordinates: y grows downward → invert Y for natural overlay feel\n    ax.invert_yaxis()\n    ax.set_aspect(\"equal\")\n    ax.set_title(f\"{track_parquet.stem} — {action} | frames {start_f}–{stop_f}\"\n                 + (f\" | bodypart='{bodypart}'\" if bodypart else \" | centroid\"))\n    ax.set_xlabel(\"x (pixels)\")\n    ax.set_ylabel(\"y (pixels)\")\n    ax.legend(\n        loc=\"upper left\",          # anchor point of the legend box itself\n        bbox_to_anchor=(1.02, 1),  # position relative to the axes (1.02 → just outside right)\n        borderaxespad=0,           # small padding\n        frameon=True               # optional: show border\n    )\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.482809Z","iopub.execute_input":"2025-10-14T05:22:17.483121Z","iopub.status.idle":"2025-10-14T05:22:17.507972Z","shell.execute_reply.started":"2025-10-14T05:22:17.483094Z","shell.execute_reply":"2025-10-14T05:22:17.506496Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"video_id = \"44566106\"  # example\ntrack_path = f\"{track_dir}/{video_id_exp}.parquet\"\nann_path   =  f\"{annotation_dir}/{video_id_exp}.parquet\"\n\n# 1) Plot the first annotation using centroid across bodyparts\nplot_action_trajectory(track_path, ann_path)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.509071Z","iopub.execute_input":"2025-10-14T05:22:17.509347Z","iopub.status.idle":"2025-10-14T05:22:17.885499Z","shell.execute_reply.started":"2025-10-14T05:22:17.509324Z","shell.execute_reply":"2025-10-14T05:22:17.884304Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"annotation_data[(annotation_data.agent_id == 1)& (annotation_data.target_id== 3)].sort_values(\"start_frame\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.886574Z","iopub.execute_input":"2025-10-14T05:22:17.886896Z","iopub.status.idle":"2025-10-14T05:22:17.904174Z","shell.execute_reply.started":"2025-10-14T05:22:17.886874Z","shell.execute_reply":"2025-10-14T05:22:17.902525Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_action_trajectory(track_path, ann_path, action_idx = 222)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:17.905580Z","iopub.execute_input":"2025-10-14T05:22:17.905947Z","iopub.status.idle":"2025-10-14T05:22:18.331371Z","shell.execute_reply.started":"2025-10-14T05:22:17.905915Z","shell.execute_reply":"2025-10-14T05:22:18.330244Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"annotation_data[(annotation_data.agent_id == 3)& (annotation_data.target_id== 1)].sort_values(\"start_frame\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:18.332669Z","iopub.execute_input":"2025-10-14T05:22:18.333034Z","iopub.status.idle":"2025-10-14T05:22:18.347080Z","shell.execute_reply.started":"2025-10-14T05:22:18.332998Z","shell.execute_reply":"2025-10-14T05:22:18.345862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_action_trajectory(track_path, ann_path, action_idx = 221)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:18.348183Z","iopub.execute_input":"2025-10-14T05:22:18.348661Z","iopub.status.idle":"2025-10-14T05:22:18.768373Z","shell.execute_reply.started":"2025-10-14T05:22:18.348633Z","shell.execute_reply":"2025-10-14T05:22:18.767321Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"annotation_data[(annotation_data.agent_id == 1)& (annotation_data.target_id== 1)]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:18.769315Z","iopub.execute_input":"2025-10-14T05:22:18.769617Z","iopub.status.idle":"2025-10-14T05:22:18.784736Z","shell.execute_reply.started":"2025-10-14T05:22:18.769585Z","shell.execute_reply":"2025-10-14T05:22:18.783606Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_action_trajectory(track_path, ann_path, action_idx = 287)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:18.785949Z","iopub.execute_input":"2025-10-14T05:22:18.786252Z","iopub.status.idle":"2025-10-14T05:22:19.087609Z","shell.execute_reply.started":"2025-10-14T05:22:18.786230Z","shell.execute_reply":"2025-10-14T05:22:19.086545Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Find Reciprocal Behavior","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\ndef find_reciprocal_behaviors(df_ann, min_overlap_frames=1):\n    \"\"\"\n    Return all pairs of annotations where agent A->B overlaps in time with B->A.\n    Works per-video; ensure df_ann is for one video_id.\n    \"\"\"\n    df = df_ann.copy()\n\n    # Normalize ints\n    for c in [\"agent_id\", \"target_id\", \"start_frame\", \"stop_frame\"]:\n        df[c] = pd.to_numeric(df[c], errors=\"coerce\")\n\n    # Exclude self behaviors (A==B) for reciprocity\n    df = df[df[\"agent_id\"] != df[\"target_id\"]].reset_index(drop=True)\n\n    # Prepare a swapped copy for the self-merge (B->A)\n    df_swapped = df.rename(columns={\n        \"agent_id\": \"target_id\",\n        \"target_id\": \"agent_id\",\n        \"action\": \"action_rev\",\n        \"start_frame\": \"start_frame_rev\",\n        \"stop_frame\":  \"stop_frame_rev\"\n    })\n\n    # Self-merge on swapped agent/target (A->B matched with B->A)\n    merged = df.merge(\n        df_swapped,\n        on=[\"agent_id\", \"target_id\"],  # (A,B) in left matches (B,A) in right AFTER renaming\n        how=\"inner\",\n        suffixes=(\"\", \"_drop\")\n    )\n\n    # Time-overlap test: max(starts) <= min(stops) and overlap length >= min_overlap_frames\n    start_max = merged[[\"start_frame\", \"start_frame_rev\"]].max(axis=1)\n    stop_min  = merged[[\"stop_frame\",  \"stop_frame_rev\"]].min(axis=1)\n    merged[\"overlap_len\"] = (stop_min - start_max + 1).clip(lower=0)\n\n    # Keep only overlapping intervals\n    out = merged[merged[\"overlap_len\"] >= min_overlap_frames].copy()\n\n    # Optional: interval IoU-like measure (useful for filtering)\n    union_len = (\n        merged[[\"stop_frame\", \"stop_frame_rev\"]].max(axis=1) -\n        merged[[\"start_frame\", \"start_frame_rev\"]].min(axis=1) + 1\n    )\n    out[\"overlap_iou\"] = out[\"overlap_len\"] / union_len\n\n    # Keep just the essentials, tidy columns\n    cols = [\n        \"agent_id\", \"target_id\",\n        \"action\", \"start_frame\", \"stop_frame\",\n        \"action_rev\", \"start_frame_rev\", \"stop_frame_rev\",\n        \"overlap_len\", \"overlap_iou\"\n    ]\n    return out[cols].sort_values([\"agent_id\", \"target_id\", \"start_frame\", \"start_frame_rev\"]).reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:19.088833Z","iopub.execute_input":"2025-10-14T05:22:19.089159Z","iopub.status.idle":"2025-10-14T05:22:19.099130Z","shell.execute_reply.started":"2025-10-14T05:22:19.089137Z","shell.execute_reply":"2025-10-14T05:22:19.097817Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"recip = find_reciprocal_behaviors(annotation_data, min_overlap_frames=1)\nrecip","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:19.100450Z","iopub.execute_input":"2025-10-14T05:22:19.100817Z","iopub.status.idle":"2025-10-14T05:22:19.167721Z","shell.execute_reply.started":"2025-10-14T05:22:19.100784Z","shell.execute_reply":"2025-10-14T05:22:19.166505Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pair_counts = (\n    recip.groupby([\"action\", \"action_rev\"])\n         .size()\n         .reset_index(name=\"count\")\n         .sort_values(\"count\", ascending=False)\n)\nprint(pair_counts.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:19.168745Z","iopub.execute_input":"2025-10-14T05:22:19.169031Z","iopub.status.idle":"2025-10-14T05:22:19.181617Z","shell.execute_reply.started":"2025-10-14T05:22:19.169011Z","shell.execute_reply":"2025-10-14T05:22:19.180402Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"same_action = recip[recip[\"action\"] == recip[\"action_rev\"]]\nsame_action","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:19.182505Z","iopub.execute_input":"2025-10-14T05:22:19.182906Z","iopub.status.idle":"2025-10-14T05:22:19.211667Z","shell.execute_reply.started":"2025-10-14T05:22:19.182879Z","shell.execute_reply":"2025-10-14T05:22:19.210737Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9) Mouse Trajectory Visualization with Annoteted segments","metadata":{}},{"cell_type":"code","source":"# --- Define the plotting function ---\ndef plot_mouse_trajectory(\n    df: pd.DataFrame,\n    frame_col: str = \"frame\",\n    x_col: str = \"x\",\n    y_col: str = \"y\",\n    label_col: str = \"label\",\n    title: str = \"Mouse Trajectory by Frame & Annotation\",\n    colors: dict | None = None,\n    show_unlabeled: bool = True,\n):\n    \"\"\"Plot mouse trajectory segmented by annotation labels.\"\"\"\n    data = df.copy().sort_values(by=frame_col).reset_index(drop=True)\n\n    def _norm_label(v):\n        if pd.isna(v) or (isinstance(v, str) and v.strip() == \"\"):\n            return None\n        return v\n\n    data[\"_label_norm\"] = data[label_col].apply(_norm_label)\n\n    unique_labels = [l for l in data[\"_label_norm\"].dropna().unique().tolist()]\n    if colors is None:\n        tab10 = plt.get_cmap(\"tab10\")\n        auto_colors = {lab: tab10(i % 10) for i, lab in enumerate(unique_labels)}\n    else:\n        auto_colors = colors.copy()\n\n    unlabeled_style = dict(color=\"0.5\", linestyle=\"--\", linewidth=1.5)\n\n    segments = []\n    if not data.empty:\n        seg_start_idx = 0\n        for i in range(1, len(data)):\n            prev = data.iloc[i-1]\n            cur = data.iloc[i]\n            label_changed = (prev[\"_label_norm\"] != cur[\"_label_norm\"])\n            frame_gap = (cur[frame_col] - prev[frame_col]) > 1\n            if label_changed or frame_gap:\n                segments.append((seg_start_idx, i-1))\n                seg_start_idx = i\n        segments.append((seg_start_idx, len(data)-1))\n\n    fig, ax = plt.subplots(figsize=(8, 6))\n    seen_for_legend = set()\n\n    for (i0, i1) in segments:\n        seg = data.iloc[i0:i1+1]\n        lab = seg[\"_label_norm\"].iloc[0]\n        if (lab is None) and not show_unlabeled:\n            continue\n\n        if lab is None:\n            style = unlabeled_style\n            leg_text = \"Unlabeled\"\n        else:\n            style = dict(color=auto_colors.get(lab, None), linestyle=\"-\", linewidth=2.5)\n            leg_text = str(lab)\n\n        label_kw = leg_text if leg_text not in seen_for_legend else None\n        if label_kw is not None:\n            seen_for_legend.add(leg_text)\n\n        ax.plot(seg[x_col].values, seg[y_col].values, marker=None, label=label_kw, **style)\n\n    # start_row = data.iloc[0]\n    # end_row = data.iloc[-1]\n    # ax.scatter([start_row[x_col]], [start_row[y_col]], s=100, facecolors=\"none\", edgecolors=\"black\", linewidths=2, marker=\"o\", label=\"Start\")\n    # ax.scatter([end_row[x_col]], [end_row[y_col]], s=100, c=\"black\", marker=\"x\", linewidths=2, label=\"End\")\n\n    ax.set_title(title)\n    ax.set_xlabel(\"X position (pixels)\")\n    ax.set_ylabel(\"Y position (pixels)\")\n    ax.invert_yaxis()\n    ax.grid(True, alpha=0.3)\n    ax.legend(\n        loc=\"upper left\",          # anchor point of the legend box itself\n        bbox_to_anchor=(1.02, 1),  # position relative to the axes (1.02 → just outside right)\n        borderaxespad=0,           # small padding\n        frameon=True               # optional: show border\n    )\n    plt.tight_layout()\n    return fig, ax\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:19.212744Z","iopub.execute_input":"2025-10-14T05:22:19.213089Z","iopub.status.idle":"2025-10-14T05:22:19.236104Z","shell.execute_reply.started":"2025-10-14T05:22:19.213060Z","shell.execute_reply":"2025-10-14T05:22:19.234950Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"video_id = \"44566106\"  # example\ntrack_path = f\"{track_dir}/{video_id_exp}.parquet\"\nann_path   =  f\"{annotation_dir}/{video_id_exp}.parquet\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:19.237102Z","iopub.execute_input":"2025-10-14T05:22:19.237470Z","iopub.status.idle":"2025-10-14T05:22:19.266531Z","shell.execute_reply.started":"2025-10-14T05:22:19.237440Z","shell.execute_reply":"2025-10-14T05:22:19.265152Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Load & Plot from Parquet (MABe schema) ===\nTRACK_PQ = track_path     # path to  tracking parquet\nANN_PQ   = ann_path         # path to  annotation parquet\nMOUSE_ID = 3                               # which mouse to visualize\nROLE     = \"agent\"                       # \"target\" | \"agent\" | \"either\"\nBODYPART = \"body_center\"                  # which bodypart to use for (x,y)\n\n# Load parquet tables (expects columns as provided in your message)\ntrack = pd.read_parquet(TRACK_PQ)\nann   = pd.read_parquet(ANN_PQ)\n\n# Filter to the selected mouse & bodypart, and standardize columns to [frame, x, y]\npos = (\n    track[(track[\"mouse_id\"] == MOUSE_ID) & (track[\"bodypart\"] == BODYPART)]\n        .rename(columns={\"video_frame\": \"frame\"})\n        .loc[:, [\"frame\", \"x\", \"y\"]]\n        .sort_values(\"frame\")\n        .drop_duplicates(\"frame\")\n        .reset_index(drop=True)\n)\n\n# Build frame-level labels from annotation intervals\nif ROLE == \"target\":\n    ann_role = ann[ann[\"target_id\"] == MOUSE_ID].copy()\nelif ROLE == \"agent\":\n    ann_role = ann[ann[\"agent_id\"] == MOUSE_ID].copy()\nelse:\n    # either: mark if mouse appears as either target or agent, prefixing the role\n    ann_role = ann[(ann[\"target_id\"] == MOUSE_ID) | (ann[\"agent_id\"] == MOUSE_ID)].copy()\n    ann_role[\"action\"] = ann_role.apply(\n        lambda r: (\"agent:\" if r[\"agent_id\"] == MOUSE_ID else \"target:\") + str(r[\"action\"]), axis=1\n    )\n\n# Initialize labels as None (unannotated)\nlabels = pd.Series(index=pos[\"frame\"].values, data=[None] * len(pos), dtype=object)\n\n# Paint actions over their [start_frame, stop_frame] intervals (inclusive)\nfor _, r in ann_role.iterrows():\n    start, stop = int(r[\"start_frame\"]), int(r[\"stop_frame\"])  # inclusive bounds\n    action = str(r[\"action\"])\n    mask = (pos[\"frame\"] >= start) & (pos[\"frame\"] <= stop)\n    # Later rows overwrite earlier ones if intervals overlap\n    labels.loc[pos.loc[mask, \"frame\"]] = action\n\n# Attach labels to positions\npos[\"label\"] = pos[\"frame\"].map(labels)\n\n# Plot\nfig, ax = plot_mouse_trajectory(\n    pos,\n    frame_col=\"frame\",\n    x_col=\"x\",\n    y_col=\"y\",\n    label_col=\"label\",\n    title=f\"Mouse {MOUSE_ID} ({ROLE}) — {BODYPART}\", \n    show_unlabeled = False\n)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:19.267781Z","iopub.execute_input":"2025-10-14T05:22:19.268102Z","iopub.status.idle":"2025-10-14T05:22:21.767287Z","shell.execute_reply.started":"2025-10-14T05:22:19.268075Z","shell.execute_reply":"2025-10-14T05:22:21.766134Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9) Look at test data","metadata":{}},{"cell_type":"code","source":"test_parquet = pd.read_parquet('/kaggle/input/MABe-mouse-behavior-detection/test_tracking/AdaptableSnail/438887472.parquet')\ntest_parquet                             ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:21.768443Z","iopub.execute_input":"2025-10-14T05:22:21.768690Z","iopub.status.idle":"2025-10-14T05:22:21.976809Z","shell.execute_reply.started":"2025-10-14T05:22:21.768669Z","shell.execute_reply":"2025-10-14T05:22:21.975776Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_sample = pd.read_csv('/kaggle/input/MABe-mouse-behavior-detection/test.csv')\ntest_sample","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:21.978040Z","iopub.execute_input":"2025-10-14T05:22:21.978405Z","iopub.status.idle":"2025-10-14T05:22:22.000646Z","shell.execute_reply.started":"2025-10-14T05:22:21.978375Z","shell.execute_reply":"2025-10-14T05:22:21.999727Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample = pd.read_csv('/kaggle/input/MABe-mouse-behavior-detection/sample_submission.csv')\nsample","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T05:22:22.001680Z","iopub.execute_input":"2025-10-14T05:22:22.001987Z","iopub.status.idle":"2025-10-14T05:22:22.017585Z","shell.execute_reply.started":"2025-10-14T05:22:22.001966Z","shell.execute_reply":"2025-10-14T05:22:22.016546Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}