{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"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":10445,"databundleVersionId":200747,"sourceType":"competition"}],"dockerImageVersionId":30587,"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\nfor 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_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 数据探索","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:01:42.842288Z","iopub.execute_input":"2023-11-21T03:01:42.843545Z","iopub.status.idle":"2023-11-21T03:01:43.338370Z","shell.execute_reply.started":"2023-11-21T03:01:42.843487Z","shell.execute_reply":"2023-11-21T03:01:43.337093Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"os.listdir('/kaggle/input/elo-merchant-category-recommendation')\n# 查看竞赛提供的文件","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:02:48.571422Z","iopub.execute_input":"2023-11-21T03:02:48.571856Z","iopub.status.idle":"2023-11-21T03:02:48.582780Z","shell.execute_reply.started":"2023-11-21T03:02:48.571821Z","shell.execute_reply":"2023-11-21T03:02:48.581414Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/elo-merchant-category-recommendation/train.csv')\ntest =  pd.read_csv('/kaggle/input/elo-merchant-category-recommendation/test.csv')\nsubmission = pd.read_csv('/kaggle/input/elo-merchant-category-recommendation/sample_submission.csv')\nprint(train.shape, test.shape, submission.shape)","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:04:02.728802Z","iopub.execute_input":"2023-11-21T03:04:02.729336Z","iopub.status.idle":"2023-11-21T03:04:03.390763Z","shell.execute_reply.started":"2023-11-21T03:04:02.729296Z","shell.execute_reply":"2023-11-21T03:04:03.389582Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train.head()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:04:28.831855Z","iopub.execute_input":"2023-11-21T03:04:28.832413Z","iopub.status.idle":"2023-11-21T03:04:28.861688Z","shell.execute_reply.started":"2023-11-21T03:04:28.832377Z","shell.execute_reply":"2023-11-21T03:04:28.860488Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test.head()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:04:47.279712Z","iopub.execute_input":"2023-11-21T03:04:47.280299Z","iopub.status.idle":"2023-11-21T03:04:47.298002Z","shell.execute_reply.started":"2023-11-21T03:04:47.280246Z","shell.execute_reply":"2023-11-21T03:04:47.296566Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission.head()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:05:01.904450Z","iopub.execute_input":"2023-11-21T03:05:01.904950Z","iopub.status.idle":"2023-11-21T03:05:01.918212Z","shell.execute_reply.started":"2023-11-21T03:05:01.904914Z","shell.execute_reply":"2023-11-21T03:05:01.916768Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 查看训练集与测试集字段的含义\ndf = pd.read_excel('/kaggle/input/elo-merchant-category-recommendation/Data Dictionary.xlsx', sheet_name='train', skiprows=2)\nfor va in df.values:\n    print('Column:', va[0], ' Description:', va[1])\n# 结论：三个feature都是匿名的信用卡离散字段，还有一个首次购物的月份，而标签是在历史和评估时期后的\n# 两个月进行量化计算得到的忠诚度分数(这里的evaluation period应该是指new_merchant_transactions.csv中的信息，同时\n# 也是对应Data_Dictionary.xlsx里面的new_merchant_period字段)","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:05:54.662290Z","iopub.execute_input":"2023-11-21T03:05:54.663465Z","iopub.status.idle":"2023-11-21T03:05:55.381924Z","shell.execute_reply.started":"2023-11-21T03:05:54.663411Z","shell.execute_reply":"2023-11-21T03:05:55.380607Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"assert train['card_id'].nunique() == train['card_id'].shape[0]\nassert test['card_id'].nunique() == test['card_id'].shape[0]\nassert test['card_id'].nunique()+ train['card_id'].nunique()  == len(set(test['card_id'].values.tolist()+train['card_id'].values.tolist()))\n# 结论：训练集与测试集的card_id均为唯一值，且训练集与测试集的card_id无重复","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:08:43.295360Z","iopub.execute_input":"2023-11-21T03:08:43.295827Z","iopub.status.idle":"2023-11-21T03:08:43.589412Z","shell.execute_reply.started":"2023-11-21T03:08:43.295794Z","shell.execute_reply":"2023-11-21T03:08:43.588056Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 检查缺失情况\ntrain.isnull().sum()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:08:51.730479Z","iopub.execute_input":"2023-11-21T03:08:51.730909Z","iopub.status.idle":"2023-11-21T03:08:51.786678Z","shell.execute_reply.started":"2023-11-21T03:08:51.730875Z","shell.execute_reply":"2023-11-21T03:08:51.785478Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test.isnull().sum()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:09:43.517580Z","iopub.execute_input":"2023-11-21T03:09:43.518018Z","iopub.status.idle":"2023-11-21T03:09:43.555242Z","shell.execute_reply.started":"2023-11-21T03:09:43.517983Z","shell.execute_reply":"2023-11-21T03:09:43.553956Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 以first_active_month为例分析训练集与测试集的差异\nfeatures = ['first_active_month','feature_1','feature_2','feature_3']\ntrain_count = train.shape[0]\ntest_count = test.shape[0]\nfor feature in features:\n    train[feature].value_counts().sort_index().plot()\n    test[feature].value_counts().sort_index().plot()\n    plt.xlabel(feature)\n    plt.legend(['train','test'])\n    plt.ylabel('count')\n    plt.show()\n#结论：训练集与测试集在所有单变量上的绝对数量分布形状极其相似，需要进一步查看相对占比分布","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:10:42.206942Z","iopub.execute_input":"2023-11-21T03:10:42.207623Z","iopub.status.idle":"2023-11-21T03:10:43.594459Z","shell.execute_reply.started":"2023-11-21T03:10:42.207575Z","shell.execute_reply":"2023-11-21T03:10:43.593338Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 以first_active_month为例分析训练集与测试集的差异，之前是频次，这里是除后的比例\nfeatures = ['first_active_month','feature_1','feature_2','feature_3']\ntrain_count = train.shape[0]\ntest_count = test.shape[0]\nfor feature in features:\n    (train[feature].value_counts().sort_index()/train_count).plot()\n    (test[feature].value_counts().sort_index()/test_count).plot()\n    plt.legend(['train','test'])\n    plt.xlabel(feature)\n    plt.ylabel('ratio')\n    plt.show()\n#结论：训练集与测试集在所有单变量上的相对占比分布形状基本一致，猜想训练集与测试集的生成方式\n# 一样，继续验证联合分布以加强猜想的事实依据\n#TODO：这里画图分析有一不严谨之处，即训练集与测试集的单变量取值范围可能不完全一样，由此两根线画\n#在同一张图上有可能会出错，如发生偏移等，请参赛者自行验证二者的横坐标是否完全一样？如果不一样，运行\n#这段代码会发生什么？在下面的联合分布验证中，我们将会填补这一遗漏之处","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:12:02.153055Z","iopub.execute_input":"2023-11-21T03:12:02.153529Z","iopub.status.idle":"2023-11-21T03:12:03.342733Z","shell.execute_reply.started":"2023-11-21T03:12:02.153478Z","shell.execute_reply":"2023-11-21T03:12:03.341340Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 为了查看多变量的联合分布，通常来说可以使用散点图，但这里的四个特征均是离散特征，因此散点图不太适合\n# 继续延续上面画单变量图的思想，参赛者可以通过将两个变量拼到一起转变为单变量的分布\ndef combine_feature(df):\n    cols = df.columns\n    feature1 = df[cols[0]].astype(str).values.tolist()\n    feature2 = df[cols[1]].astype(str).values.tolist()\n    return pd.Series([feature1[i]+'&'+feature2[i] for i in range(df.shape[0])])\nn = len(features)\nfor i in range(n-1):\n    for j in range(i+1, n):\n        cols = [features[i], features[j]]\n        print(cols)\n        train_dis = combine_feature(train[cols]).value_counts().sort_index()/train_count\n        test_dis = combine_feature(test[cols]).value_counts().sort_index()/test_count\n        index_dis = pd.Series(train_dis.index.tolist() + test_dis.index.tolist()).drop_duplicates().sort_values()\n        (index_dis.map(train_dis).fillna(0)).plot()\n        (index_dis.map(train_dis).fillna(0)).plot()\n        plt.legend(['train','test'])\n        plt.xlabel('&'.join(cols))\n        plt.ylabel('ratio')\n        plt.show()\n# 结论：修正上述遗漏后参赛者可以发现训练集与测试集的两变量联合分布也基本保持一致，由此基本可以判定，训练集与测似集\n# 的生成方式基本一摸一样，即测试集与训练集是同一批数据随机划分后的结果，有兴趣的参赛者可继续验证三变量和四变量分布。假定\n# 关于验证集与测试集的这一猜想成立，会极大地增添参赛者后续进行特征工程的信心，对建模方式也会有一个整体把握。","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:15:17.156001Z","iopub.execute_input":"2023-11-21T03:15:17.156478Z","iopub.status.idle":"2023-11-21T03:15:22.746746Z","shell.execute_reply.started":"2023-11-21T03:15:17.156416Z","shell.execute_reply":"2023-11-21T03:15:22.745341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 最后再来看看本次赛题的关键目标target,由于是忠诚度分数为连续值，通常借助pandas的describe函数进行简单统计\ntrain['target'].describe()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:16:23.019121Z","iopub.execute_input":"2023-11-21T03:16:23.019560Z","iopub.status.idle":"2023-11-21T03:16:23.048492Z","shell.execute_reply.started":"2023-11-21T03:16:23.019528Z","shell.execute_reply":"2023-11-21T03:16:23.047077Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train['target'].value_counts().head()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:16:42.413066Z","iopub.execute_input":"2023-11-21T03:16:42.413523Z","iopub.status.idle":"2023-11-21T03:16:42.455960Z","shell.execute_reply.started":"2023-11-21T03:16:42.413487Z","shell.execute_reply":"2023-11-21T03:16:42.454662Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 此外，关于连续值参赛者还可以借助直方图进行频域观察\ntrain['target'].plot.hist()\nplt.show()\n# 结论：忠诚度分数target绝大多数集中在[-1,1]的区间","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:17:05.904275Z","iopub.execute_input":"2023-11-21T03:17:05.904697Z","iopub.status.idle":"2023-11-21T03:17:06.209641Z","shell.execute_reply.started":"2023-11-21T03:17:05.904663Z","shell.execute_reply":"2023-11-21T03:17:06.208515Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\n# 查看商家信息字段的含义\ndf = pd.read_excel('/kaggle/input/elo-merchant-category-recommendation/Data_Dictionary.xlsx', sheet_name='merchant', skiprows=2)\nprint(df.shape)\nfor va in df.values:\n    print('Column:', va[0], ' Description:', va[1])\n# 结论：可以看出商家信息表除了基本的类别数值字段，主办方还提供了商家在最近时期的各种交易量","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:19:41.259053Z","iopub.execute_input":"2023-11-21T03:19:41.259519Z","iopub.status.idle":"2023-11-21T03:19:41.296618Z","shell.execute_reply.started":"2023-11-21T03:19:41.259482Z","shell.execute_reply":"2023-11-21T03:19:41.295537Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 首先检查商家信息数据的正确性\nmerchant = pd.read_csv('/kaggle/input/elo-merchant-category-recommendation/merchants.csv')\nprint(merchant.shape, merchant['merchant_id'].nunique())\nprint(pd.Series(merchant.columns.tolist()).sort_values().values ==  pd.Series([va[0] for va in df.values]).sort_values().values)\n# 结论: 特征信息完全对上，且商家信息记录数据表不唯一，后续处理需要注意","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:21:04.647931Z","iopub.execute_input":"2023-11-21T03:21:04.648338Z","iopub.status.idle":"2023-11-21T03:21:06.740971Z","shell.execute_reply.started":"2023-11-21T03:21:04.648306Z","shell.execute_reply":"2023-11-21T03:21:06.739741Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"merchant.head()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:21:23.223133Z","iopub.execute_input":"2023-11-21T03:21:23.223527Z","iopub.status.idle":"2023-11-21T03:21:23.254256Z","shell.execute_reply.started":"2023-11-21T03:21:23.223497Z","shell.execute_reply":"2023-11-21T03:21:23.252860Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 根据商家信息表字段的离散和连续属性，参赛者可将两类字段分开后统一化分析，现字段分离如下\ncategory_cols = ['merchant_id', 'merchant_group_id', 'merchant_category_id',\n       'subsector_id', 'category_1',\n       'most_recent_sales_range', 'most_recent_purchases_range',\n       'category_4', 'city_id', 'state_id', 'category_2']\nnumeric_cols = ['numerical_1', 'numerical_2',\n     'avg_sales_lag3', 'avg_purchases_lag3', 'active_months_lag3',\n       'avg_sales_lag6', 'avg_purchases_lag6', 'active_months_lag6',\n       'avg_sales_lag12', 'avg_purchases_lag12', 'active_months_lag12']\nassert len(category_cols) + len(numeric_cols) == merchant.shape[1]","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:22:02.889832Z","iopub.execute_input":"2023-11-21T03:22:02.890226Z","iopub.status.idle":"2023-11-21T03:22:02.897504Z","shell.execute_reply.started":"2023-11-21T03:22:02.890195Z","shell.execute_reply":"2023-11-21T03:22:02.895808Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"merchant[category_cols].dtypes","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:22:26.331308Z","iopub.execute_input":"2023-11-21T03:22:26.331752Z","iopub.status.idle":"2023-11-21T03:22:26.371642Z","shell.execute_reply.started":"2023-11-21T03:22:26.331716Z","shell.execute_reply":"2023-11-21T03:22:26.369552Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"merchant[category_cols].nunique()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:22:35.717016Z","iopub.execute_input":"2023-11-21T03:22:35.717495Z","iopub.status.idle":"2023-11-21T03:22:36.012684Z","shell.execute_reply.started":"2023-11-21T03:22:35.717419Z","shell.execute_reply":"2023-11-21T03:22:36.011462Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 简单起见，且由于机器学习模型不支持object类型的变量，参赛者可将离散字段进行编码，根据字段含义，object型的变量要么只有两个值\n# （'category_1'，'category_4' ），要么就是含有字典序，因此参赛者可按照字典序对object型变量先编码处理(商家id除外)\n\ndef change_object_cols(se):\n    value = se.unique().tolist()\n    value.sort()\n    return se.map(pd.Series(range(len(value)), index=value)).values\nfor col in ['category_1', 'most_recent_sales_range', 'most_recent_purchases_range', 'category_4']:\n    merchant[col] = change_object_cols(merchant[col])","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:25:08.587815Z","iopub.execute_input":"2023-11-21T03:25:08.588241Z","iopub.status.idle":"2023-11-21T03:25:08.807400Z","shell.execute_reply.started":"2023-11-21T03:25:08.588209Z","shell.execute_reply":"2023-11-21T03:25:08.806356Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 同时为了能够更方便统计，首先做缺失值处理，对离散字段可统一用-1进行填充\nmerchant[category_cols].isnull().sum()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:25:28.486376Z","iopub.execute_input":"2023-11-21T03:25:28.487214Z","iopub.status.idle":"2023-11-21T03:25:28.564984Z","shell.execute_reply.started":"2023-11-21T03:25:28.487152Z","shell.execute_reply":"2023-11-21T03:25:28.563576Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"merchant[category_cols] = merchant[category_cols].fillna(-1)","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:25:54.204798Z","iopub.execute_input":"2023-11-21T03:25:54.205322Z","iopub.status.idle":"2023-11-21T03:25:54.312179Z","shell.execute_reply.started":"2023-11-21T03:25:54.205276Z","shell.execute_reply":"2023-11-21T03:25:54.309775Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 简单看下每个离散字段Top10的数量分布\nfor col in category_cols[1:]:\n    merchant[col].astype(str).value_counts()[:10].sort_index().plot.bar()\n    plt.show()\n# 结论：在建模思路还不明确的情况下，参赛者可适当进行数据探查，也不必盲目面面俱到，做到心中有数\n# 对数据结构有个清晰的了解，以便后续快速进行构思与方案验证","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:26:12.204987Z","iopub.execute_input":"2023-11-21T03:26:12.205460Z","iopub.status.idle":"2023-11-21T03:26:17.942007Z","shell.execute_reply.started":"2023-11-21T03:26:12.205411Z","shell.execute_reply":"2023-11-21T03:26:17.940764Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 对离散型字段探查发现有正无穷值inf，这也是模型所不能接受的，因此需要对无限值进行处理\nmerchant[numeric_cols].describe()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:27:51.015494Z","iopub.execute_input":"2023-11-21T03:27:51.015946Z","iopub.status.idle":"2023-11-21T03:27:51.204316Z","shell.execute_reply.started":"2023-11-21T03:27:51.015908Z","shell.execute_reply":"2023-11-21T03:27:51.203132Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#将无穷替换为-99\ninf_cols = ['avg_purchases_lag3', 'avg_purchases_lag6', 'avg_purchases_lag12']\nmerchant[inf_cols] = merchant[inf_cols].replace(np.inf, merchant[inf_cols].replace(np.inf, -99).max().max())","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:28:20.868250Z","iopub.execute_input":"2023-11-21T03:28:20.868703Z","iopub.status.idle":"2023-11-21T03:28:20.895097Z","shell.execute_reply.started":"2023-11-21T03:28:20.868667Z","shell.execute_reply":"2023-11-21T03:28:20.893731Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 对于离散字段的缺失值处理方式也有多样，这里先使用平均值进行填充，后续有需要再进行优化处理\nfor col in numeric_cols:\n    merchant[col] = merchant[col].fillna(merchant[col].mean())","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:28:47.334891Z","iopub.execute_input":"2023-11-21T03:28:47.335286Z","iopub.status.idle":"2023-11-21T03:28:47.372236Z","shell.execute_reply.started":"2023-11-21T03:28:47.335257Z","shell.execute_reply":"2023-11-21T03:28:47.370992Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"merchant[numeric_cols].describe()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:30:45.576220Z","iopub.execute_input":"2023-11-21T03:30:45.576666Z","iopub.status.idle":"2023-11-21T03:30:45.786233Z","shell.execute_reply.started":"2023-11-21T03:30:45.576633Z","shell.execute_reply":"2023-11-21T03:30:45.785022Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 最后是本次竞赛最重要的用户交易信息记录数据，可以看到有new_merchant_transactions.csv与historical_transactions.csv\n# 查看交易记录表信息字段的含义\ndf = pd.read_excel('/kaggle/input/elo-merchant-category-recommendation/Data_Dictionary.xlsx', sheet_name='history', skiprows=2)\nprint(df.shape)\nfor va in df.values:\n    print('Column:', va[0], ' Description:', va[1])\n    \ndf = pd.read_excel('/kaggle/input/elo-merchant-category-recommendation/Data_Dictionary.xlsx', sheet_name='new_merchant_period', skiprows=2)\nprint(df.shape)\nfor va in df.values:\n    print('Column:', va[0], ' Description:', va[1])\n# 结论：可以看出两张交易记录信息表字段一致，且除了数量与金额之外均为类别字段\n# 可按照与商家信息表类似的方式对交易记录表进行简单分析，但由于交易记录表的id索引并不唯一\n# 所有的类别字段都值得进行探索分析，当然也可以顺带进行数据的处理\n# 特别注意：有时离散字段与连续字段之间的分界并不明显，有的字段既可以作为离散字段处理，又可以\n# 作为连续字段进行处理，此外，这里出现了很特别的一个字段就是时间，这里我们会专门讲解时间字段的处理","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:31:53.369088Z","iopub.execute_input":"2023-11-21T03:31:53.369591Z","iopub.status.idle":"2023-11-21T03:31:53.425525Z","shell.execute_reply.started":"2023-11-21T03:31:53.369551Z","shell.execute_reply":"2023-11-21T03:31:53.424320Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"new_transaction = pd.read_csv('/kaggle/input/elo-merchant-category-recommendation/new_merchant_transactions.csv')","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:33:52.666643Z","iopub.execute_input":"2023-11-21T03:33:52.667220Z","iopub.status.idle":"2023-11-21T03:34:00.815659Z","shell.execute_reply.started":"2023-11-21T03:33:52.667180Z","shell.execute_reply":"2023-11-21T03:34:00.814224Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"new_transaction['installments'].describe()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:34:07.965711Z","iopub.execute_input":"2023-11-21T03:34:07.966148Z","iopub.status.idle":"2023-11-21T03:34:08.026838Z","shell.execute_reply.started":"2023-11-21T03:34:07.966110Z","shell.execute_reply":"2023-11-21T03:34:08.025134Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"history_transaction = pd.read_csv('/kaggle/input/elo-merchant-category-recommendation/historical_transactions.csv')","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:35:04.472811Z","iopub.execute_input":"2023-11-21T03:35:04.473220Z","iopub.status.idle":"2023-11-21T03:37:08.573999Z","shell.execute_reply.started":"2023-11-21T03:35:04.473188Z","shell.execute_reply":"2023-11-21T03:37:08.572003Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"new_transaction.head()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:37:08.607570Z","iopub.execute_input":"2023-11-21T03:37:08.607959Z","iopub.status.idle":"2023-11-21T03:37:08.630825Z","shell.execute_reply.started":"2023-11-21T03:37:08.607926Z","shell.execute_reply":"2023-11-21T03:37:08.629119Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"numeric_cols = [ 'installments', 'month_lag', 'purchase_amount']\ncategory_cols = ['authorized_flag', 'card_id', 'city_id', 'category_1',\n       'category_3', 'merchant_category_id', 'merchant_id', 'category_2', 'state_id',\n       'subsector_id']\ntime_cols = ['purchase_date']\nassert len(numeric_cols) + len(category_cols) + len(time_cols) == new_transaction.shape[1]","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:42:08.842464Z","iopub.execute_input":"2023-11-21T03:42:08.843012Z","iopub.status.idle":"2023-11-21T03:42:08.850823Z","shell.execute_reply.started":"2023-11-21T03:42:08.842972Z","shell.execute_reply":"2023-11-21T03:42:08.849211Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 对比可以发现与商家信息id有重复字段，因此确认二者含义是否一致\nduplicate_cols = []\nfor col in merchant.columns:\n    if col in new_transaction.columns:\n        duplicate_cols.append(col)\nprint(duplicate_cols)","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:42:34.966629Z","iopub.execute_input":"2023-11-21T03:42:34.967203Z","iopub.status.idle":"2023-11-21T03:42:34.974908Z","shell.execute_reply.started":"2023-11-21T03:42:34.967158Z","shell.execute_reply":"2023-11-21T03:42:34.973700Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(new_transaction[duplicate_cols].drop_duplicates().shape)\nprint(new_transaction.shape)\nprint(new_transaction['merchant_id'].nunique())\nprint(merchant['merchant_id'].nunique(), merchant.shape)\n# 结论：由此可以看出交易记录表中的merhcant_id相关信息并不唯一，有可能会发生动态变化如评分等级等等\n# 因此优先建议使用交易记录表中的相应记录\nmerchant[duplicate_cols].head()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:43:30.168777Z","iopub.execute_input":"2023-11-21T03:43:30.169245Z","iopub.status.idle":"2023-11-21T03:43:32.393054Z","shell.execute_reply.started":"2023-11-21T03:43:30.169208Z","shell.execute_reply":"2023-11-21T03:43:32.391834Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"new_transaction[duplicate_cols].head()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:45:18.578073Z","iopub.execute_input":"2023-11-21T03:45:18.578617Z","iopub.status.idle":"2023-11-21T03:45:18.674658Z","shell.execute_reply.started":"2023-11-21T03:45:18.578574Z","shell.execute_reply":"2023-11-21T03:45:18.673425Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"new_transaction[category_cols].head()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:46:10.967738Z","iopub.execute_input":"2023-11-21T03:46:10.968166Z","iopub.status.idle":"2023-11-21T03:46:11.231884Z","shell.execute_reply.started":"2023-11-21T03:46:10.968133Z","shell.execute_reply":"2023-11-21T03:46:11.230470Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 可仿照merchant的处理方式对字符型的离散特征进行编码（id除外）\nfor col in ['authorized_flag', 'category_1', 'category_3']:\n    new_transaction[col] = change_object_cols(new_transaction[col].fillna(-1).astype(str))\nnew_transaction[category_cols] = new_transaction[category_cols].fillna(-1)","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:46:26.996986Z","iopub.execute_input":"2023-11-21T03:46:26.997395Z","iopub.status.idle":"2023-11-21T03:46:29.789978Z","shell.execute_reply.started":"2023-11-21T03:46:26.997362Z","shell.execute_reply":"2023-11-21T03:46:29.788513Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"category_cols","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:46:44.466035Z","iopub.execute_input":"2023-11-21T03:46:44.466620Z","iopub.status.idle":"2023-11-21T03:46:44.475530Z","shell.execute_reply.started":"2023-11-21T03:46:44.466573Z","shell.execute_reply":"2023-11-21T03:46:44.474376Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 由于两张交易表是根据信用卡用户消费的时间来进行区分，因此有必要着重分析这二者之间的区别与联系\nprint(new_transaction['month_lag'].describe())\nprint('\\n')\nprint(new_transaction['purchase_date'].min(), 'to', new_transaction['purchase_date'].max())","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:47:29.979104Z","iopub.execute_input":"2023-11-21T03:47:29.980998Z","iopub.status.idle":"2023-11-21T03:47:31.009865Z","shell.execute_reply.started":"2023-11-21T03:47:29.980941Z","shell.execute_reply":"2023-11-21T03:47:31.008255Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(history_transaction['month_lag'].describe())\nprint('\\n')\nprint(history_transaction['purchase_date'].min(), 'to', history_transaction['purchase_date'].max())","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:48:47.956628Z","iopub.execute_input":"2023-11-21T03:48:47.957150Z","iopub.status.idle":"2023-11-21T03:48:58.512347Z","shell.execute_reply.started":"2023-11-21T03:48:47.957112Z","shell.execute_reply":"2023-11-21T03:48:58.510688Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 看到这里参赛者难免会有些疑惑，所谓历史与最新的交易整体时间窗差距两个月，但并不是代表每个用户的交易记录时间均处在同一时间窗，因此有必要重新审视一下用户的注册日期\nprint(train['first_active_month'].min(), train['first_active_month'].max())\nprint(test['first_active_month'].dropna().min(), test['first_active_month'].dropna().max())","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:49:40.723553Z","iopub.execute_input":"2023-11-21T03:49:40.724024Z","iopub.status.idle":"2023-11-21T03:49:40.877788Z","shell.execute_reply.started":"2023-11-21T03:49:40.723985Z","shell.execute_reply":"2023-11-21T03:49:40.876550Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#分析每个用户在存在交易月份数量上的差异\nhistory_transaction.groupby('card_id')['month_lag'].nunique().describe()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:50:21.202745Z","iopub.execute_input":"2023-11-21T03:50:21.203424Z","iopub.status.idle":"2023-11-21T03:50:33.225770Z","shell.execute_reply.started":"2023-11-21T03:50:21.203377Z","shell.execute_reply":"2023-11-21T03:50:33.224552Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 关于时间的处理上，可以按照一些字符串操作提取相应的信息，比如直接提取年份、月份、日期和小时点等等\n# 还有一种相对万能可适用于各种场景的办法就是使用unix时间戳，可以灵活用于各种转换与计算\nnew_transaction[new_transaction['month_lag']==1]['purchase_date'].apply(lambda x:'-'.join(x.split(' ')[0].split('-')[:2])).value_counts()","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:50:46.914279Z","iopub.execute_input":"2023-11-21T03:50:46.915601Z","iopub.status.idle":"2023-11-21T03:50:48.710875Z","shell.execute_reply.started":"2023-11-21T03:50:46.915544Z","shell.execute_reply":"2023-11-21T03:50:48.709628Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"history_transaction[history_transaction['month_lag']==0]['purchase_date'].apply(lambda x:'-'.join(x.split(' ')[0].split('-')[:2])).value_counts()\n#结论：由上面可知month_lag是一个相对值，每个信用卡id的month_lag=0的月份都不一样，即用户最新一次购物的时间不一样\n#显然越是出现在最新日期的用户越有可能粘性越强价值越高，也就是题目所衡量的越忠诚","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:51:02.687802Z","iopub.execute_input":"2023-11-21T03:51:02.688269Z","iopub.status.idle":"2023-11-21T03:51:09.892951Z","shell.execute_reply.started":"2023-11-21T03:51:02.688220Z","shell.execute_reply":"2023-11-21T03:51:09.891759Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 在熟悉基本比赛数据后，直觉上参赛者可考虑使用RFM分析，即采用recent、frequency、money进行\n# 目标列相关性分析,简单起见，这里以new_transaction为例，由于都是最近记录因此就不统计recent\ncard_rfm = train[['card_id', 'target']].copy()\n\ncard_rfm['frequency'] = card_rfm['card_id'].map(new_transaction['card_id'].value_counts()).fillna(0).values\ncard_rfm['money'] = card_rfm['card_id'].map(new_transaction.groupby('card_id')['purchase_amount'].sum()).fillna(0).values\n\ncard_rfm = card_rfm.sort_values('target').reset_index(drop=True)\nfor col in ['target', 'frequency', 'money']:\n    card_rfm[col].plot.hist()\n    plt.show()\n    \n# 结论：可以看出，target高的用户集中在频次与消费都高的区域，这个发现也比较符合逻辑","metadata":{"execution":{"iopub.status.busy":"2023-11-21T03:51:31.665676Z","iopub.execute_input":"2023-11-21T03:51:31.666119Z","iopub.status.idle":"2023-11-21T03:51:34.662359Z","shell.execute_reply.started":"2023-11-21T03:51:31.666084Z","shell.execute_reply":"2023-11-21T03:51:34.660807Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 总结：通过基本的数据探索性分析，相信参赛者对数据以及对赛题任务都有了一个良好的理解，相信\n# 本赛题的重点便是挖掘用户的各种交易行为与目标列的关系，进而达到良好的模型学习效果，准确预测\n# 测试集用户的忠诚度分数。因此这是一个关注信用卡用户局部消费偏好画像的题目，通过找到相似的训练集用户来\n# 类推测试集用户的忠诚度分数，进而对高价值人群进行区分，给商家与信用卡银行提供决策支持，同时也\n# 提升消费者体验，由此特征工程可集中于用户的交易行为画像","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 数据预处理","metadata":{}},{"cell_type":"code","source":"import gc\nimport time\nimport numpy as np\nimport pandas as pd\nimport lightgbm as lgb\nfrom datetime import datetime","metadata":{"execution":{"iopub.status.busy":"2023-11-22T03:01:17.367535Z","iopub.execute_input":"2023-11-22T03:01:17.368187Z","iopub.status.idle":"2023-11-22T03:01:17.372057Z","shell.execute_reply.started":"2023-11-22T03:01:17.368152Z","shell.execute_reply":"2023-11-22T03:01:17.371341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/elo-merchant-category-recommendation/train.csv')\ntest =  pd.read_csv('/kaggle/input/elo-merchant-category-recommendation/test.csv')\nmerchant = pd.read_csv('/kaggle/input/elo-merchant-category-recommendation/merchants.csv')\nnew_transaction = pd.read_csv('/kaggle/input/elo-merchant-category-recommendation/new_merchant_transactions.csv')\nhistory_transaction = pd.read_csv('/kaggle/input/elo-merchant-category-recommendation/historical_transactions.csv')","metadata":{"execution":{"iopub.status.busy":"2023-11-22T03:01:19.120414Z","iopub.execute_input":"2023-11-22T03:01:19.120691Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 按照字典序进行编码\ndef change_object_cols(se):\n    value = se.unique().tolist()\n    value.sort()\n    return se.map(pd.Series(range(len(value)), index=value)).values","metadata":{"execution":{"iopub.status.busy":"2023-11-22T02:56:54.795947Z","iopub.execute_input":"2023-11-22T02:56:54.796322Z","iopub.status.idle":"2023-11-22T02:56:54.801259Z","shell.execute_reply.started":"2023-11-22T02:56:54.796296Z","shell.execute_reply":"2023-11-22T02:56:54.800383Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# train&test\n# 对首次活跃月份进行编码\nse_map = change_object_cols(pd.concat([train['first_active_month'], test['first_active_month']]).astype(str))\ntrain['first_active_month'] = se_map[:train.shape[0]]\ntest['first_active_month'] = se_map[train.shape[0]:]","metadata":{"execution":{"iopub.status.busy":"2023-11-22T02:56:56.960026Z","iopub.execute_input":"2023-11-22T02:56:56.960398Z","iopub.status.idle":"2023-11-22T02:56:57.016266Z","shell.execute_reply.started":"2023-11-22T02:56:56.960372Z","shell.execute_reply":"2023-11-22T02:56:57.015390Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## 商户信息\n# 1、根据业务含义划分离散字段category_cols与连续字段numeric_cols。\ncategory_cols = ['merchant_id', 'merchant_group_id', 'merchant_category_id',\n       'subsector_id', 'category_1',\n       'most_recent_sales_range', 'most_recent_purchases_range',\n       'category_4', 'city_id', 'state_id', 'category_2']\nnumeric_cols = ['numerical_1', 'numerical_2',\n     'avg_sales_lag3', 'avg_purchases_lag3', 'active_months_lag3',\n       'avg_sales_lag6', 'avg_purchases_lag6', 'active_months_lag6',\n       'avg_sales_lag12', 'avg_purchases_lag12', 'active_months_lag12']\n\n# 2、对非数值型的离散字段进行字典排序编码。\nfor col in ['category_1', 'most_recent_sales_range', 'most_recent_purchases_range', 'category_4']:\n    merchant[col] = change_object_cols(merchant[col])\n    \n# 3、为了能够更方便统计，进行缺失值的处理，对离散字段统一用-1进行填充。\nmerchant[category_cols] = merchant[category_cols].fillna(-1)\n\n\n# 4、对离散型字段探查发现有正无穷值，这是特征提取以及模型所不能接受的，因此需要对无限值进行处理，此处采用最大值进行替换。\ninf_cols = ['avg_purchases_lag3', 'avg_purchases_lag6', 'avg_purchases_lag12']\nmerchant[inf_cols] = merchant[inf_cols].replace(np.inf, merchant[inf_cols].replace(np.inf, -99).max().max())\n\n# 5、对于离散字段的缺失值处理方式也有多样，这里先使用平均值进行填充，后续有需要再进行优化处理。\nfor col in numeric_cols:\n    merchant[col] = merchant[col].fillna(merchant[col].mean())\n    \n# 6、去除与transaction交易记录表格重复的列，以及merchant_id的重复记录。\nduplicate_cols = ['merchant_id', 'merchant_category_id', 'subsector_id', 'category_1', 'city_id', 'state_id', 'category_2']\nmerchant = merchant.drop(duplicate_cols[1:], axis=1)\nmerchant = merchant.loc[merchant['merchant_id'].drop_duplicates().index.tolist()].reset_index(drop=True)","metadata":{"execution":{"iopub.status.busy":"2023-11-22T02:56:59.173984Z","iopub.execute_input":"2023-11-22T02:56:59.174868Z","iopub.status.idle":"2023-11-22T02:56:59.505063Z","shell.execute_reply.started":"2023-11-22T02:56:59.174781Z","shell.execute_reply":"2023-11-22T02:56:59.503890Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## 交易记录\n# 1、为了统一处理，首先拼接new和history两张表格，后续可以month_lag>=0进行区分。\ntransaction = pd.concat([new_transaction, history_transaction], axis=0, ignore_index=True)\ndel new_transaction\ndel history_transaction\ngc.collect()\n\n# 2、同样划分离散字段、连续字段以及时间字段。\nnumeric_cols = [ 'installments', 'month_lag', 'purchase_amount']\ncategory_cols = ['authorized_flag', 'card_id', 'city_id', 'category_1',\n       'category_3', 'merchant_category_id', 'merchant_id', 'category_2', 'state_id',\n       'subsector_id']\ntime_cols = ['purchase_date']\n\n# 3、可仿照merchant的处理方式对字符型的离散特征进行字典序编码以及缺失值填充。\nfor col in ['authorized_flag', 'category_1', 'category_3']:\n    transaction[col] = change_object_cols(transaction[col].fillna(-1).astype(str))\ntransaction[category_cols] = transaction[category_cols].fillna(-1)\ntransaction['category_2'] = transaction['category_2'].astype(int)\n\n# 4、进行时间段的处理，简单起见进行月份、日期的星期数（工作日与周末）、以及\n# 时间段（上午、下午、晚上、凌晨）的信息提取。\ntransaction['purchase_month'] = transaction['purchase_date'].apply(lambda x:'-'.join(x.split(' ')[0].split('-')[:2]))\ntransaction['purchase_hour_section'] = transaction['purchase_date'].apply(lambda x: x.split(' ')[1].split(':')[0]).astype(int)//6\ntransaction['purchase_day'] = transaction['purchase_date'].apply(lambda x: datetime.strptime(x.split(\" \")[0], \"%Y-%m-%d\").weekday())//5                                                                    \ndel transaction['purchase_date']\n\n\n# 5、对新生成的购买月份离散字段进行字典序编码。\ntransaction['purchase_month'] = change_object_cols(transaction['purchase_month'].fillna(-1).astype(str))","metadata":{"execution":{"iopub.status.busy":"2023-11-22T02:57:02.050777Z","iopub.execute_input":"2023-11-22T02:57:02.051170Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## 表格合并\n# 6、处理完transaction与merchant两个表格后，为了方便特征的统一计算将其merge合并，重新划分相应字段种类。\ncols = ['merchant_id', 'most_recent_sales_range', 'most_recent_purchases_range', 'category_4']\ntransaction = pd.merge(transaction, merchant[cols], how='left', on='merchant_id')\n\nnumeric_cols = ['purchase_amount', 'installments']\n\ncategory_cols = ['authorized_flag', 'city_id', 'category_1',\n       'category_3', 'merchant_category_id','month_lag','most_recent_sales_range',\n                 'most_recent_purchases_range', 'category_4',\n                 'purchase_month', 'purchase_hour_section', 'purchase_day']\n\nid_cols = ['card_id', 'merchant_id']\n\ntransaction[cols[1:]] = transaction[cols[1:]].fillna(-1).astype(int)\ntransaction[category_cols] =transaction[category_cols].fillna(-1).astype(str)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 特征提取","metadata":{}},{"cell_type":"code","source":"## 初步策略，第一步采用字典结构统计用户的所有行为特征\n## 第二步，用户层面横向进行比较和特征提取\n## 第三步，纵向对比所有用户的交易记录\n\nfeatures = {}\ncard_all = train['card_id'].append(test['card_id']).values.tolist()\nfor card in card_all:\n    features[card] = {}\n    \n\ncolumns = transaction.columns.tolist()\nidx = columns.index('card_id')\ncategory_cols_index = [columns.index(col) for col in category_cols]\nnumeric_cols_index = [columns.index(col) for col in numeric_cols]\n\n\n\n# 记录运行时间\ns = time.time()\nnum = 0\nfor i in range(transaction.shape[0]):\n    va = transaction.loc[i].values\n    card = va[idx]\n    for cate_ind in category_cols_index:\n        for num_ind in numeric_cols_index:\n            col_name = '&'.join([columns[cate_ind], va[cate_ind], columns[num_ind]])\n            features[card][col_name] = features[card].get(col_name, 0) + va[num_ind]\n    num += 1\n    if num%1000000==0:\n        print(time.time()-s, \"s\")\ndel transaction\ngc.collect()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 字典转dataframe\ndf = pd.DataFrame(features).T.reset_index()\ndel features\ncols = df.columns.tolist()\ndf.columns = ['card_id'] + cols[1:]\n# 生成训练集与测试集\ntrain = pd.merge(train, df, how='left', on='card_id')\ntest =  pd.merge(test, df, how='left', on='card_id')\ndel df\ntrain.to_csv(\"preprocess/train_dict.csv\", index=False)\ntest.to_csv(\"preprocess/test_dict.csv\", index=False)","metadata":{},"execution_count":null,"outputs":[]}]}