{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.14"},"papermill":{"default_parameters":{},"duration":1774.347036,"end_time":"2022-06-07T17:11:35.142176","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2022-06-07T16:42:00.79514","version":"2.3.4"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":35332,"databundleVersionId":3723648,"sourceType":"competition"},{"sourceId":3739819,"sourceType":"datasetVersion","datasetId":2231132}],"dockerImageVersionId":30787,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"### In this notebook, we use cudf pandas to create a bunch of useful features and train XGB models. The entire pipeline is slow due to CPU.","metadata":{"papermill":{"duration":0.006268,"end_time":"2022-06-07T16:42:10.201779","exception":false,"start_time":"2022-06-07T16:42:10.195511","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"### What you might find useful from this notebook:\n### - After-pay features. It makes intuitive sense that subtracting the payments from balance/spend etc provides new information about the users' behavior.\n### - Feature selection and hyperparameter tuning. Hundreds of GPU hours are burned to get these numbers.","metadata":{"papermill":{"duration":0.004732,"end_time":"2022-06-07T16:42:10.2112","exception":false,"start_time":"2022-06-07T16:42:10.206468","status":"completed"},"tags":[]}},{"cell_type":"code","source":"#%load_ext cudf.pandas","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:12:01.703067Z","iopub.execute_input":"2024-10-17T21:12:01.704066Z","iopub.status.idle":"2024-10-17T21:12:02.020776Z","shell.execute_reply.started":"2024-10-17T21:12:01.704011Z","shell.execute_reply":"2024-10-17T21:12:02.019891Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings('ignore')","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:12:02.022018Z","iopub.execute_input":"2024-10-17T21:12:02.022533Z","iopub.status.idle":"2024-10-17T21:12:02.027417Z","shell.execute_reply.started":"2024-10-17T21:12:02.022495Z","shell.execute_reply":"2024-10-17T21:12:02.02627Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\nimport pandas as pd\nfrom time import time\nimport xgboost as xgb\nimport numpy as np\nfrom tqdm import tqdm\nfrom collections import Counter, defaultdict","metadata":{"papermill":{"duration":4.652739,"end_time":"2022-06-07T16:42:14.902096","exception":false,"start_time":"2022-06-07T16:42:10.249357","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-10-17T21:12:02.028936Z","iopub.execute_input":"2024-10-17T21:12:02.029402Z","iopub.status.idle":"2024-10-17T21:12:02.757021Z","shell.execute_reply.started":"2024-10-17T21:12:02.029361Z","shell.execute_reply":"2024-10-17T21:12:02.755869Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Data Overview","metadata":{}},{"cell_type":"code","source":"import os\npath='/kaggle/input'\nfor dirname, _, filenames in os.walk(path):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","papermill":{"duration":0.028713,"end_time":"2022-06-07T16:42:10.244497","exception":false,"start_time":"2022-06-07T16:42:10.215784","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-10-17T21:12:02.759824Z","iopub.execute_input":"2024-10-17T21:12:02.760439Z","iopub.status.idle":"2024-10-17T21:12:02.771927Z","shell.execute_reply.started":"2024-10-17T21:12:02.760396Z","shell.execute_reply":"2024-10-17T21:12:02.770739Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"```\nThe objective of this competition is to predict the probability that a customer does not pay back their credit card balance amount in the future based on their monthly customer profile. The target binary variable is calculated by observing 18 months performance window after the latest credit card statement, and if the customer does not pay due amount in 120 days after their latest statement date it is considered a default event.\n```","metadata":{}},{"cell_type":"markdown","source":"```\nYour task is to predict, for each customer_ID, the probability of a future payment default (target = 1).\n\nNote that the negative class has been subsampled for this dataset at 5%, and thus receives a 20x weighting in the scoring metric.\n```","metadata":{}},{"cell_type":"markdown","source":"```\nEvaluation\nThe evaluation metric,\n𝑀, for this competition is the mean of two measures of rank ordering: Normalized Gini Coefficient,\n𝐺, and default rate captured at 4%,\n𝐷, The default rate captured at 4% is the percentage of the positive labels (defaults) captured within the highest-ranked 4% of the predictions, and represents a Sensitivity/Recall statistic.\n\n𝑀=0.5⋅(𝐺+𝐷)\n\nThe default rate captured at 4% is the percentage of the positive labels (defaults) captured within the highest-ranked 4% of the predictions, and represents a Sensitivity/Recall statistic.\n```","metadata":{}},{"cell_type":"code","source":"train_label = pd.read_csv(f'{path}/amex-default-prediction/train_labels.csv')\ntrain_label.head()","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:12:02.773485Z","iopub.execute_input":"2024-10-17T21:12:02.773839Z","iopub.status.idle":"2024-10-17T21:12:04.065906Z","shell.execute_reply.started":"2024-10-17T21:12:02.773801Z","shell.execute_reply":"2024-10-17T21:12:04.064761Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_label.target.value_counts()","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:12:04.067415Z","iopub.execute_input":"2024-10-17T21:12:04.067939Z","iopub.status.idle":"2024-10-17T21:12:04.274504Z","shell.execute_reply.started":"2024-10-17T21:12:04.067885Z","shell.execute_reply":"2024-10-17T21:12:04.273393Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"```\nThe dataset contains aggregated profile features for each customer at each statement date. Features are anonymized and normalized, and fall into the following general categories:\n\nD_* = Delinquency variables\nS_* = Spend variables\nP_* = Payment variables\nB_* = Balance variables\nR_* = Risk variables\n```","metadata":{}},{"cell_type":"code","source":"%%time\npath='/kaggle/input'\ndf = pd.read_parquet(f'{path}/amex-data-integer-dtypes-parquet-format/train.parquet')","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:12:04.275656Z","iopub.execute_input":"2024-10-17T21:12:04.275981Z","iopub.status.idle":"2024-10-17T21:12:16.4828Z","shell.execute_reply.started":"2024-10-17T21:12:04.275947Z","shell.execute_reply":"2024-10-17T21:12:16.481745Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.head()","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:12:16.484188Z","iopub.execute_input":"2024-10-17T21:12:16.484612Z","iopub.status.idle":"2024-10-17T21:12:19.633262Z","shell.execute_reply.started":"2024-10-17T21:12:16.484572Z","shell.execute_reply":"2024-10-17T21:12:19.63194Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Select two columns from each group","metadata":{}},{"cell_type":"code","source":"tag_groups = defaultdict(list)\nlst = df.columns\ncols = [s for s in lst if len(tag_groups[s.split('_')[0]]) < 2 and not tag_groups[s.split('_')[0]].append(s)]\ncols = sorted(cols, reverse=True)\ndf.head()[cols]","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:12:19.634938Z","iopub.execute_input":"2024-10-17T21:12:19.635312Z","iopub.status.idle":"2024-10-17T21:12:19.944162Z","shell.execute_reply.started":"2024-10-17T21:12:19.635273Z","shell.execute_reply":"2024-10-17T21:12:19.942892Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Feature Engineering","metadata":{"papermill":{"duration":0.004859,"end_time":"2022-06-07T16:42:14.912329","exception":false,"start_time":"2022-06-07T16:42:14.90747","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"#### Groupby-Aggregation-Merge feature engineering","metadata":{}},{"cell_type":"markdown","source":"<a href=\"link.com\"><img src=\"https://jakevdp.github.io/PythonDataScienceHandbook/figures/03.08-split-apply-combine.png\" alt=\"Alt text\"></a>","metadata":{}},{"cell_type":"markdown","source":"#### Difference between two features","metadata":{}},{"cell_type":"markdown","source":"```python\n# compute \"after pay\" features\n        for bcol in [f'B_{i}' for i in [11,14,17]]+['D_39','D_131']+[f'S_{i}' for i in [16,23]]:\n            for pcol in ['P_2','P_3']:\n                if bcol in df.columns:\n                    df[f'{bcol}-{pcol}'] = df[bcol] - df[pcol]\n```","metadata":{}},{"cell_type":"markdown","source":"#### Found by random search with cross-validation ","metadata":{}},{"cell_type":"markdown","source":"```\nI launch a random search of many trials, where in each trial a small number of non-payment numeric features are randomly selected to subtract payment features and run the cross-validation again along with the original features. If the cross-validation score beats the baseline and the new after-pay features end up in the top 10 most important features, they are marked as good features for subtracting payments. After running hundreds of trials, only a handful of such good features met this criterion. I just took all the good features and put them together in a list and train the model again as the notebook does.\n```","metadata":{}},{"cell_type":"markdown","source":"#### Cross validation","metadata":{}},{"cell_type":"markdown","source":"<a href=\"link.com\"><img src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/LOOCV.gif/800px-LOOCV.gif?20200304222121\" alt=\"Alt text\"></a>","metadata":{}},{"cell_type":"code","source":"def get_not_used():\n    # cid is the label encode of customer_ID\n    # row_id indicates the order of rows\n    return ['row_id', 'customer_ID', 'target', 'cid', 'S_2']\n    \ndef preprocess(df, FEATURE_ENGINEERING):\n    df['row_id'] = np.arange(df.shape[0])\n    not_used = get_not_used()\n    cat_cols = ['B_30', 'B_38', 'D_114', 'D_116', 'D_117', 'D_120',\n                'D_126', 'D_63', 'D_64', 'D_66', 'D_68']\n\n    if FEATURE_ENGINEERING=='on':\n        for col in df.columns:\n            if col not in not_used+cat_cols:\n                df[col] = df[col].round(2)\n    \n        # compute \"after pay\" features\n        for bcol in [f'B_{i}' for i in [11,14,17]]+['D_39','D_131']+[f'S_{i}' for i in [16,23]]:\n            for pcol in ['P_2','P_3']:\n                if bcol in df.columns:\n                    df[f'{bcol}-{pcol}'] = df[bcol] - df[pcol]\n\n    df['S_2'] = pd.to_datetime(df['S_2'])\n    df['cid'], _ = df.customer_ID.factorize()\n        \n    num_cols = [col for col in df.columns if col not in cat_cols+not_used]\n    \n    dgs = add_stats_step(df, num_cols)\n        \n    # merge might change row orders\n    # restore the original row order by sorting row_id\n    df = df.sort_values('row_id')\n    df = df.drop(['row_id'],axis=1)\n    return df, dgs\n\ndef add_stats_step(df, cols):\n    n = 50\n    dgs = []\n    for i in range(0,len(cols),n):\n        s = i\n        e = min(s+n, len(cols))\n        dg = add_stats_one_shot(df, cols[s:e])\n        dgs.append(dg)\n    return dgs\n\ndef add_stats_one_shot(df, cols):\n    stats = ['mean','std']\n    dg = df.groupby('customer_ID').agg({col:stats for col in cols})\n    out_cols = []\n    for col in cols:\n        out_cols.extend([f'{col}_{s}' for s in stats])\n    dg.columns = out_cols\n    dg = dg.reset_index()\n    return dg\n\ndef process_data(df, FEATURE_ENGINEERING):\n    df,dgs = preprocess(df, FEATURE_ENGINEERING)\n    df = df.drop_duplicates('customer_ID',keep='last')\n    if FEATURE_ENGINEERING == 'on':\n        for dg in dgs:\n            df = df.merge(dg, on='customer_ID', how='left')\n        diff_cols = [col for col in df.columns if col.endswith('_diff')]\n        df = df.drop(diff_cols,axis=1)\n    return df\n\ndef load_train(path, FEATURE_ENGINEERING):\n    train = pd.read_parquet(f'{path}/amex-data-integer-dtypes-parquet-format/train.parquet')\n    \n    train = process_data(train, FEATURE_ENGINEERING)\n    trainl = pd.read_csv(f'{path}/amex-default-prediction/train_labels.csv')\n    train = train.merge(trainl, on='customer_ID', how='left')\n    return train\n\n\ndef bold_print(x):\n    print(f\"\\033[1m{x}\\033[0m\")","metadata":{"papermill":{"duration":0.035014,"end_time":"2022-06-07T16:42:14.952292","exception":false,"start_time":"2022-06-07T16:42:14.917278","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-10-17T21:12:19.945417Z","iopub.execute_input":"2024-10-17T21:12:19.945775Z","iopub.status.idle":"2024-10-17T21:12:19.965649Z","shell.execute_reply.started":"2024-10-17T21:12:19.945732Z","shell.execute_reply":"2024-10-17T21:12:19.96446Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### XGB Params and utility functions","metadata":{"papermill":{"duration":0.00492,"end_time":"2022-06-07T16:42:14.962224","exception":false,"start_time":"2022-06-07T16:42:14.957304","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def xgb_train(x, y, xt, yt):\n    bold_print(f\"# of features: {x.shape[1]}\")\n    assert x.shape[1] == xt.shape[1]\n    dtrain = xgb.DMatrix(data=x, label=y)\n    dvalid = xgb.DMatrix(data=xt, label=yt)\n    params = {\n            'objective': 'binary:logistic', \n            'tree_method': 'hist', \n            'max_depth': 7,\n            'subsample':0.88,\n            'colsample_bytree': 0.5,\n            'gamma':1.5,\n            'min_child_weight':8,\n            'lambda':70,\n            'eta':0.1,\n    }\n    watchlist = [(dtrain, 'train'), (dvalid, 'eval')]\n    bst = xgb.train(params, dtrain=dtrain,\n                num_boost_round=50,evals=watchlist,\n                early_stopping_rounds=500, feval=xgb_amex, maximize=True,\n                verbose_eval=50)\n    #return _,bst\n    print('best ntree_limit:', bst.best_iteration)\n    print('best score:', bst.best_score)\n    return bst.predict(dvalid, iteration_range=(0,bst.best_iteration)), bst","metadata":{"papermill":{"duration":0.017364,"end_time":"2022-06-07T16:42:14.984338","exception":false,"start_time":"2022-06-07T16:42:14.966974","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-10-17T21:12:19.967273Z","iopub.execute_input":"2024-10-17T21:12:19.967664Z","iopub.status.idle":"2024-10-17T21:12:19.979302Z","shell.execute_reply.started":"2024-10-17T21:12:19.967621Z","shell.execute_reply":"2024-10-17T21:12:19.97803Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Metrics","metadata":{"papermill":{"duration":0.004691,"end_time":"2022-06-07T16:42:14.994005","exception":false,"start_time":"2022-06-07T16:42:14.989314","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def xgb_amex(y_pred, y_true):\n    return 'amex', amex_metric_np(y_pred,y_true.get_label())\n\n# Created by https://www.kaggle.com/yunchonggan\n# https://www.kaggle.com/competitions/amex-default-prediction/discussion/328020\ndef amex_metric_np(preds: np.ndarray, target: np.ndarray) -> float:\n    indices = np.argsort(preds)[::-1]\n    preds, target = preds[indices], target[indices]\n\n    weight = 20.0 - target * 19.0\n    cum_norm_weight = (weight / weight.sum()).cumsum()\n    four_pct_mask = cum_norm_weight <= 0.04\n    d = np.sum(target[four_pct_mask]) / np.sum(target)\n\n    weighted_target = target * weight\n    lorentz = (weighted_target / weighted_target.sum()).cumsum()\n    gini = ((lorentz - cum_norm_weight) * weight).sum()\n\n    n_pos = np.sum(target)\n    n_neg = target.shape[0] - n_pos\n    gini_max = 10 * n_neg * (n_pos + 20 * n_neg - 19) / (n_pos + 20 * n_neg)\n\n    g = gini / gini_max\n    return 0.5 * (g + d)\n\n# we still need the official metric since the faster version above is slightly off\nimport pandas as pd\ndef amex_metric(y_true: pd.DataFrame, y_pred: pd.DataFrame) -> float:\n\n    def top_four_percent_captured(y_true: pd.DataFrame, y_pred: pd.DataFrame) -> float:\n        df = (pd.concat([y_true, y_pred], axis='columns')\n              .sort_values('prediction', ascending=False))\n        df['weight'] = df['target'].apply(lambda x: 20 if x==0 else 1)\n        four_pct_cutoff = int(0.04 * df['weight'].sum())\n        df['weight_cumsum'] = df['weight'].cumsum()\n        df_cutoff = df.loc[df['weight_cumsum'] <= four_pct_cutoff]\n        return (df_cutoff['target'] == 1).sum() / (df['target'] == 1).sum()\n        \n    def weighted_gini(y_true: pd.DataFrame, y_pred: pd.DataFrame) -> float:\n        df = (pd.concat([y_true, y_pred], axis='columns')\n              .sort_values('prediction', ascending=False))\n        df['weight'] = df['target'].apply(lambda x: 20 if x==0 else 1)\n        df['random'] = (df['weight'] / df['weight'].sum()).cumsum()\n        total_pos = (df['target'] * df['weight']).sum()\n        df['cum_pos_found'] = (df['target'] * df['weight']).cumsum()\n        df['lorentz'] = df['cum_pos_found'] / total_pos\n        df['gini'] = (df['lorentz'] - df['random']) * df['weight']\n        return df['gini'].sum()\n\n    def normalized_weighted_gini(y_true: pd.DataFrame, y_pred: pd.DataFrame) -> float:\n        y_true_pred = y_true.rename(columns={'target': 'prediction'})\n        return weighted_gini(y_true, y_pred) / weighted_gini(y_true, y_true_pred)\n\n    g = normalized_weighted_gini(y_true, y_pred)\n    d = top_four_percent_captured(y_true, y_pred)\n\n    return 0.5 * (g + d)","metadata":{"papermill":{"duration":0.028302,"end_time":"2022-06-07T16:42:15.027542","exception":false,"start_time":"2022-06-07T16:42:14.99924","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-10-17T21:12:19.98066Z","iopub.execute_input":"2024-10-17T21:12:19.981026Z","iopub.status.idle":"2024-10-17T21:12:20.000632Z","shell.execute_reply.started":"2024-10-17T21:12:19.980992Z","shell.execute_reply":"2024-10-17T21:12:19.999599Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Train XGB in K-folds","metadata":{"papermill":{"duration":0.00552,"end_time":"2022-06-07T16:42:46.925316","exception":false,"start_time":"2022-06-07T16:42:46.919796","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def run_cv(FEATURE_ENGINEERING):\n    start = time()\n    \n    path = '/kaggle/input'\n    train = load_train(path, FEATURE_ENGINEERING)\n    \n    bold_print(f'Feature engineering time: {time()-start:.1f} seconds')\n    \n    not_used = get_not_used()\n    not_used = [i for i in not_used if i in train.columns]\n    msgs = {}\n    folds = 4\n    score = 0\n    \n    for i in range(folds):\n        mask = train['cid']%folds == i\n        tr,va = train[~mask], train[mask]\n        \n        x, y = tr.drop(not_used, axis=1), tr['target']\n        xt, yt = va.drop(not_used, axis=1), va['target']\n        yp, bst = xgb_train(x, y, xt, yt)\n        #break\n        bst.save_model(f'xgb_{i}.json')\n        amex_score = amex_metric(pd.DataFrame({'target':yt.values}), \n                                        pd.DataFrame({'prediction':yp}))\n        msg = f\"Fold {i} amex {amex_score:.4f}\"\n        print(msg)\n        score += amex_score\n        \n    score /= folds\n    bold_print(f\"Average amex score: {score:.4f}\")\n    return train, score","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:12:20.005159Z","iopub.execute_input":"2024-10-17T21:12:20.005741Z","iopub.status.idle":"2024-10-17T21:12:20.015526Z","shell.execute_reply.started":"2024-10-17T21:12:20.005703Z","shell.execute_reply":"2024-10-17T21:12:20.014341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\nFEATURE_ENGINEERING = 'off'\ntrain, score = run_cv(FEATURE_ENGINEERING)","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:12:20.016819Z","iopub.execute_input":"2024-10-17T21:12:20.017197Z","iopub.status.idle":"2024-10-17T21:13:01.061682Z","shell.execute_reply.started":"2024-10-17T21:12:20.017138Z","shell.execute_reply":"2024-10-17T21:13:01.060612Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\nFEATURE_ENGINEERING = 'on'\ntrain_fea, score = run_cv(FEATURE_ENGINEERING)","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:13:01.063902Z","iopub.execute_input":"2024-10-17T21:13:01.064903Z","iopub.status.idle":"2024-10-17T21:15:16.342131Z","shell.execute_reply.started":"2024-10-17T21:13:01.064841Z","shell.execute_reply":"2024-10-17T21:15:16.34088Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train.head()","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:15:16.34375Z","iopub.execute_input":"2024-10-17T21:15:16.344129Z","iopub.status.idle":"2024-10-17T21:15:19.894514Z","shell.execute_reply.started":"2024-10-17T21:15:16.344094Z","shell.execute_reply":"2024-10-17T21:15:19.893352Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_fea.head()","metadata":{"execution":{"iopub.status.busy":"2024-10-17T21:15:19.896732Z","iopub.execute_input":"2024-10-17T21:15:19.897356Z","iopub.status.idle":"2024-10-17T21:15:29.435257Z","shell.execute_reply.started":"2024-10-17T21:15:19.897299Z","shell.execute_reply":"2024-10-17T21:15:29.434139Z"},"trusted":true},"execution_count":null,"outputs":[]}]}