{"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":"In this notebook, referring to [Getting Started](https://www.kaggle.com/code/robikscube/nfl-player-contact-detection-getting-started), I created a training model using xgboost with some features other than distance, such as speed and acceleration.","metadata":{}},{"cell_type":"code","source":"import os\nimport torch\n\nclass Config:\n    AUTHOR = \"colum2131\"\n\n    NAME = \"NFLC-\" + \"Exp001-simple-xgb-baseline\"\n\n    COMPETITION = \"nfl-player-contact-detection\"\n\n    seed = 42\n    num_fold = 5\n    \n    xgb_params = {\n        'objective': 'binary:logistic',\n        'eval_metric': 'auc',\n        'learning_rate':0.03,\n        'tree_method':'hist' if not torch.cuda.is_available() else 'gpu_hist'\n    }","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-01-27T06:32:30.856537Z","iopub.execute_input":"2023-01-27T06:32:30.856966Z","iopub.status.idle":"2023-01-27T06:32:32.715274Z","shell.execute_reply.started":"2023-01-27T06:32:30.856859Z","shell.execute_reply":"2023-01-27T06:32:32.714119Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport gc\nimport subprocess\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nfrom IPython.display import Video, display\n\nfrom scipy.optimize import minimize\nimport cv2\nfrom glob import glob\nfrom tqdm import tqdm\n\nfrom sklearn.model_selection import GroupKFold\nfrom sklearn.metrics import (\n    roc_auc_score,\n    matthews_corrcoef,\n)\n\nimport xgboost as xgb\n\nimport torch\n\nif torch.cuda.is_available():\n    import cupy \n    import cudf\n    from cuml import ForestInference","metadata":{"execution":{"iopub.status.busy":"2023-01-27T06:32:32.720532Z","iopub.execute_input":"2023-01-27T06:32:32.721531Z","iopub.status.idle":"2023-01-27T06:32:36.865376Z","shell.execute_reply.started":"2023-01-27T06:32:32.721477Z","shell.execute_reply":"2023-01-27T06:32:36.864221Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def setup(cfg):\n    cfg.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    \n    # set dirs\n    cfg.INPUT = f'../input/{cfg.COMPETITION}'\n    cfg.EXP = cfg.NAME\n    cfg.OUTPUT_EXP = cfg.NAME\n    cfg.SUBMISSION = './'\n    cfg.DATASET = '../input/'\n\n    cfg.EXP_MODEL = os.path.join(cfg.EXP, 'model')\n    cfg.EXP_FIG = os.path.join(cfg.EXP, 'fig')\n    cfg.EXP_PREDS = os.path.join(cfg.EXP, 'preds')\n\n    # make dirs\n    for d in [cfg.EXP_MODEL, cfg.EXP_FIG, cfg.EXP_PREDS]:\n        os.makedirs(d, exist_ok=True)\n        \n    return cfg","metadata":{"execution":{"iopub.status.busy":"2023-01-27T06:32:36.870171Z","iopub.execute_input":"2023-01-27T06:32:36.870543Z","iopub.status.idle":"2023-01-27T06:32:36.882052Z","shell.execute_reply.started":"2023-01-27T06:32:36.870506Z","shell.execute_reply":"2023-01-27T06:32:36.881070Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ==============================\n# function\n# ==============================\n# ref: https://www.kaggle.com/code/robikscube/nfl-player-contact-detection-getting-started\ndef add_contact_id(df):\n    # Create contact ids\n    df[\"contact_id\"] = (\n        df[\"game_play\"]\n        + \"_\"\n        + df[\"step\"].astype(\"str\")\n        + \"_\"\n        + df[\"nfl_player_id_1\"].astype(\"str\")\n        + \"_\"\n        + df[\"nfl_player_id_2\"].astype(\"str\")\n    )\n    return df\n\ndef expand_contact_id(df):\n    \"\"\"\n    Splits out contact_id into seperate columns.\n    \"\"\"\n    df[\"game_play\"] = df[\"contact_id\"].str[:12]\n    df[\"step\"] = df[\"contact_id\"].str.split(\"_\").str[-3].astype(\"int\")\n    df[\"nfl_player_id_1\"] = df[\"contact_id\"].str.split(\"_\").str[-2]\n    df[\"nfl_player_id_2\"] = df[\"contact_id\"].str.split(\"_\").str[-1]\n    return df\n\n# cross validation\ndef get_groupkfold(train, target_col, group_col, n_splits):\n    kf = GroupKFold(n_splits=n_splits)\n    generator = kf.split(train, train[target_col], train[group_col])\n    fold_series = []\n    for fold, (idx_train, idx_valid) in enumerate(generator):\n        fold_series.append(pd.Series(fold, index=idx_valid))\n    fold_series = pd.concat(fold_series).sort_index()\n    return fold_series\n\n# xgboost code\ndef fit_xgboost(cfg, X, y, params, add_suffix=''):\n    \"\"\"\n    xgb_params = {\n        'objective': 'binary:logistic',\n        'eval_metric': 'auc',\n        'learning_rate':0.01,\n        'tree_method':'gpu_hist'\n    }\n    \"\"\"\n    oof_pred = np.zeros(len(y), dtype=np.float32)\n    for fold in sorted(cfg.folds.unique()):\n        if fold == -1: continue\n        idx_train = (cfg.folds!=fold)\n        idx_valid = (cfg.folds==fold)\n        x_train, y_train = X[idx_train], y[idx_train]\n        x_valid, y_valid = X[idx_valid], y[idx_valid]\n        display(pd.Series(y_valid).value_counts())\n\n        xgb_train = xgb.DMatrix(x_train, label=y_train)\n        xgb_valid = xgb.DMatrix(x_valid, label=y_valid)\n        evals = [(xgb_train,'train'),(xgb_valid,'eval')]\n\n        model = xgb.train(\n            params,\n            xgb_train,\n            num_boost_round=10_000,\n            early_stopping_rounds=100,\n            evals=evals,\n            verbose_eval=100,\n        )\n\n        model_path = os.path.join(cfg.EXP_MODEL, f'xgb_fold{fold}{add_suffix}.model')\n        model.save_model(model_path)\n        if not torch.cuda.is_available():\n            model = xgb.Booster().load_model(model_path)\n        else:\n            model = ForestInference.load(model_path, output_class=True, model_type='xgboost')\n        pred_i = model.predict_proba(x_valid)[:, 1]\n        oof_pred[x_valid.index] = pred_i\n        score = round(roc_auc_score(y_valid, pred_i), 5)\n        print(f'Performance of the prediction: {score}\\n')\n        del model; gc.collect()\n\n    np.save(os.path.join(cfg.EXP_PREDS, f'oof_pred{add_suffix}'), oof_pred)\n    score = round(roc_auc_score(y, oof_pred), 5)\n    print(f'All Performance of the prediction: {score}')\n    return oof_pred\n\ndef pred_xgboost(X, data_dir, add_suffix=''):\n    models = glob(os.path.join(data_dir, f'xgb_fold*{add_suffix}.model'))\n    if not torch.cuda.is_available():\n         models = [xgb.Booster().load_model(model_path) for model in models]\n    else:\n        models = [ForestInference.load(model, output_class=True, model_type='xgboost') for model in models]\n    preds = np.array([model.predict_proba(X)[:, 1] for model in models])\n    preds = np.mean(preds, axis=0)\n    return preds","metadata":{"execution":{"iopub.status.busy":"2023-01-27T06:32:36.887530Z","iopub.execute_input":"2023-01-27T06:32:36.890018Z","iopub.status.idle":"2023-01-27T06:32:36.918074Z","shell.execute_reply.started":"2023-01-27T06:32:36.889980Z","shell.execute_reply":"2023-01-27T06:32:36.916948Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ==============================\n# read data\n# ==============================\ncfg = setup(Config)\n\nif not torch.cuda.is_available():\n    tr_tracking = pd.read_csv(os.path.join(cfg.INPUT, 'train_player_tracking.csv'), parse_dates=[\"datetime\"])\n    te_tracking = pd.read_csv(os.path.join(cfg.INPUT, 'test_player_tracking.csv'), parse_dates=[\"datetime\"])\n    # tr_helmets = pd.read_csv(os.path.join(cfg.INPUT, 'train_baseline_helmets.csv'))\n    # te_helmets = pd.read_csv(os.path.join(cfg.INPUT, 'test_baseline_helmets.csv'))\n    # tr_video_metadata = pd.read_csv(os.path.join(cfg.INPUT, 'train_video_metadata.csv'))\n    # te_video_metadata = pd.read_csv(os.path.join(cfg.INPUT, 'test_video_metadata.csv'))\n    sub = pd.read_csv(os.path.join(cfg.INPUT, 'sample_submission.csv'))\n\n    train = pd.read_csv(os.path.join(cfg.INPUT, 'train_labels.csv'), parse_dates=[\"datetime\"])\n    test = expand_contact_id(sub)\n    \nelse:\n    tr_tracking = cudf.read_csv(os.path.join(cfg.INPUT, 'train_player_tracking.csv'), parse_dates=[\"datetime\"])\n    te_tracking = cudf.read_csv(os.path.join(cfg.INPUT, 'test_player_tracking.csv'), parse_dates=[\"datetime\"])\n    # tr_helmets = cudf.read_csv(os.path.join(cfg.INPUT, 'train_baseline_helmets.csv'))\n    # te_helmets = cudf.read_csv(os.path.join(cfg.INPUT, 'test_baseline_helmets.csv'))\n    # tr_video_metadata = cudf.read_csv(os.path.join(cfg.INPUT, 'train_video_metadata.csv'))\n    # te_video_metadata = cudf.read_csv(os.path.join(cfg.INPUT, 'test_video_metadata.csv'))\n    sub = pd.read_csv(os.path.join(cfg.INPUT, 'sample_submission.csv'))\n\n    train = cudf.read_csv(os.path.join(cfg.INPUT, 'train_labels.csv'), parse_dates=[\"datetime\"])\n    test = cudf.DataFrame(expand_contact_id(sub))","metadata":{"execution":{"iopub.status.busy":"2023-01-27T06:32:36.923257Z","iopub.execute_input":"2023-01-27T06:32:36.925547Z","iopub.status.idle":"2023-01-27T06:32:50.120268Z","shell.execute_reply.started":"2023-01-27T06:32:36.925509Z","shell.execute_reply":"2023-01-27T06:32:50.119258Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The following code is used to create the features.  \nBasically, the numerical features contained in player_tracking.csv are merged into player_id_1 and player_id_2 respectively.","metadata":{}},{"cell_type":"code","source":"# ==============================\n# feature engineering\n# ==============================\ndef create_features(df, tr_tracking, merge_col=\"step\", use_cols=[\"x_position\", \"y_position\"]):\n    output_cols = []\n    df_combo = (\n        df.astype({\"nfl_player_id_1\": \"str\"})\n        .merge(\n            tr_tracking.astype({\"nfl_player_id\": \"str\"})[\n                [\"game_play\", merge_col, \"nfl_player_id\",] + use_cols\n            ],\n            left_on=[\"game_play\", merge_col, \"nfl_player_id_1\"],\n            right_on=[\"game_play\", merge_col, \"nfl_player_id\"],\n            how=\"left\",\n        )\n        .rename(columns={c: c+\"_1\" for c in use_cols})\n        .drop(\"nfl_player_id\", axis=1)\n        .merge(\n            tr_tracking.astype({\"nfl_player_id\": \"str\"})[\n                [\"game_play\", merge_col, \"nfl_player_id\"] + use_cols\n            ],\n            left_on=[\"game_play\", merge_col, \"nfl_player_id_2\"],\n            right_on=[\"game_play\", merge_col, \"nfl_player_id\"],\n            how=\"left\",\n        )\n        .drop(\"nfl_player_id\", axis=1)\n        .rename(columns={c: c+\"_2\" for c in use_cols})\n        .sort_values([\"game_play\", merge_col, \"nfl_player_id_1\", \"nfl_player_id_2\"])\n        .reset_index(drop=True)\n    )\n    output_cols += [c+\"_1\" for c in use_cols]\n    output_cols += [c+\"_2\" for c in use_cols]\n    \n    if (\"x_position\" in use_cols) & (\"y_position\" in use_cols):\n        index = df_combo['x_position_2'].notnull()\n        if torch.cuda.is_available():\n            index = index.to_array()\n        distance_arr = np.full(len(index), np.nan)\n        tmp_distance_arr = np.sqrt(\n            np.square(df_combo.loc[index, \"x_position_1\"] - df_combo.loc[index, \"x_position_2\"])\n            + np.square(df_combo.loc[index, \"y_position_1\"]- df_combo.loc[index, \"y_position_2\"])\n        )\n        if torch.cuda.is_available():\n            tmp_distance_arr = tmp_distance_arr.to_array()\n        distance_arr[index] = tmp_distance_arr\n        df_combo['distance'] = distance_arr\n        output_cols += [\"distance\"]\n        \n    df_combo['G_flug'] = (df_combo['nfl_player_id_2']==\"G\")\n    output_cols += [\"G_flug\"]\n    return df_combo, output_cols\n\n\nuse_cols = [\n    'x_position', 'y_position', 'speed', 'distance',\n    'direction', 'orientation', 'acceleration', 'sa'\n]\ntrain, feature_cols = create_features(train, tr_tracking, use_cols=use_cols)\ntest, feature_cols = create_features(test, te_tracking, use_cols=use_cols)\nif torch.cuda.is_available():\n    train = train.to_pandas()\n    test = test.to_pandas()\n\ndisplay(train)","metadata":{"execution":{"iopub.status.busy":"2023-01-27T06:32:50.122667Z","iopub.execute_input":"2023-01-27T06:32:50.123385Z","iopub.status.idle":"2023-01-27T06:32:59.199736Z","shell.execute_reply.started":"2023-01-27T06:32:50.123346Z","shell.execute_reply":"2023-01-27T06:32:59.198624Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ==============================\n# training & inference\n# ==============================\ntrain_X = train[feature_cols]\ntest_X = test[feature_cols]\ntrain_y = train['contact']\ncfg.folds = get_groupkfold(train, 'contact', 'game_play', cfg.num_fold)\ncfg.folds.to_csv(os.path.join(cfg.EXP_PREDS, 'folds.csv'), index=False)\n\noof_pred = fit_xgboost(cfg, train_X, train_y, cfg.xgb_params, add_suffix=\"_xgb_1st\")\nsub_pred = pred_xgboost(test_X, cfg.EXP_MODEL, add_suffix=\"_xgb_1st\")","metadata":{"execution":{"iopub.status.busy":"2023-01-27T06:32:59.201488Z","iopub.execute_input":"2023-01-27T06:32:59.201917Z","iopub.status.idle":"2023-01-27T06:37:19.752293Z","shell.execute_reply.started":"2023-01-27T06:32:59.201862Z","shell.execute_reply":"2023-01-27T06:37:19.751235Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ==============================\n# optimize\n# ==============================\ndef func(x_list):\n    score = matthews_corrcoef(train['contact'], oof_pred>x_list[0])\n    return -score\n\nx0 = [0.5]\nresult = minimize(func, x0,  method=\"nelder-mead\")\ncfg.threshold = result.x[0]\nprint(\"score:\", round(matthews_corrcoef(train['contact'], oof_pred>cfg.threshold), 5))\nprint(\"threshold\", round(cfg.threshold, 5))\n\ntest = add_contact_id(test)\ntest['contact'] = (sub_pred > cfg.threshold).astype(int)\ntest[['contact_id', 'contact']].to_csv('submission.csv', index=False)\ndisplay(test[['contact_id', 'contact']].head())","metadata":{"execution":{"iopub.status.busy":"2023-01-27T06:37:19.753969Z","iopub.execute_input":"2023-01-27T06:37:19.754357Z","iopub.status.idle":"2023-01-27T06:38:04.845387Z","shell.execute_reply.started":"2023-01-27T06:37:19.754320Z","shell.execute_reply":"2023-01-27T06:38:04.844358Z"},"trusted":true},"execution_count":null,"outputs":[]}]}