{"cells":[{"metadata":{"_uuid":"e346d7556fe9c6a1a07a925e74d416a536b55daf"},"cell_type":"markdown","source":"- In this notebook，we have a G-sotre datasetandwe need to predict  the natural log of the sum of all transactions per user.\n- Thanks for [the great work](http://https://www.kaggle.com/sudalairajkumar/simple-exploration-baseline-ga-customer-revenue) by [SRK](http://https://www.kaggle.com/sudalairajkumar) about the data cleanning and visualization method,and [wonderful work](http://https://www.kaggle.com/super13579/ga-v2-future-purchase-prediction) by [funkyboy](http://https://www.kaggle.com/super13579) on how to split train set."},{"metadata":{"trusted":true,"_uuid":"765f229d39c60d22c22e6d6dbc682f9553517113"},"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nimport json\nimport time\nfrom pandas.io.json import json_normalize\nimport seaborn as sns \nimport matplotlib.pyplot as plt\nimport re\nimport lightgbm as lgb\nfrom ast import literal_eval\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import TimeSeriesSplit, KFold\nfrom sklearn.metrics import mean_squared_error\nimport plotly\nfrom plotly import tools\nplotly.offline.init_notebook_mode()\nimport plotly.offline as py\nimport plotly.graph_objs as go\nfrom sklearn.metrics import accuracy_score\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.metrics import mean_squared_error\nfrom sklearn.preprocessing import LabelEncoder \nfrom sklearn.model_selection import GridSearchCV\nimport lightgbm as lgb","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"8d32533eda6bf4b1fca9143d43d04dc5e7b1d8c0"},"cell_type":"code","source":"url_train = '../input/train_v2.csv'\n\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c3806d3c0ea6733e6353a33b1f601628131c01b3"},"cell_type":"markdown","source":""},{"metadata":{"_uuid":"b86145f88c1316555499a771565cbda247a730c8"},"cell_type":"markdown","source":"#### convert  the json columns\n- json.loads()  https://docs.python.org/3/library/json.html\n- json_normalize() can normalize semi-structured JSON data into a flat table"},{"metadata":{"trusted":true,"_uuid":"cf1389185dfc225b087e784b748bec5d62cc5e9c"},"cell_type":"code","source":"def load_df(csv_path= url_train , nrows=None):\n    starttime = time.time()\n    chunks = []\n    loop = True\n    chunk_size = 1000\n    \n    columns_use = ['channelGrouping',\n                    'customDimensions',\n                    'date',\n                    'device', \n                    'fullVisitorId',\n                    'geoNetwork',\n                    'socialEngagementType',\n                    'totals',\n                    'trafficSource',\n                    'visitId',\n                    'visitNumber',\n                    'visitStartTime']\n    data = pd.read_csv(csv_path,\n                       converters={'device':json.loads,\n                                   'geoNetwork':json.loads,\n                                   'totals':json.loads,\n                                   'trafficSource':json.loads},\n                       dtype={'fullVisitorId': 'str'},                                    \n                       usecols = columns_use,\n                       iterator =True)\n\n                      \n    while loop:\n        try:\n            chunk = data.get_chunk(chunk_size)\n            chunks.append(chunk)\n        except StopIteration:\n            loop = False\n            print('Iteration is stopped')   \n    sample = pd.concat(chunks,axis = 0)\n\n    time_use = time.time() - starttime\n    print(f'use {time_use}s')\n    return sample\n\ndef json_split(df):\n    JSON_COLUMNS = ['device','geoNetwork','totals','trafficSource']\n    for column in JSON_COLUMNS:\n        columns_as_df = json_normalize(df[column].values.tolist())\n        columns_as_df.columns = [f\"{column}_{subcolumn}\" for subcolumn in columns_as_df.columns]\n        df = df.drop(column, axis=1).merge(columns_as_df, right_index=True, left_index=True)\n    return df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"32f5fa59bc07d9a24ac7cce38cc331b1a6513c04"},"cell_type":"code","source":"train_df = load_df()\ntrain_df = json_split(train_df)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a3b59bfdc752a2c17c540ddfc19e05b0384c4b85"},"cell_type":"code","source":"train_df.shape","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9db5effefb6425bdea0adf9760b48a1af8098da9"},"cell_type":"markdown","source":"- df.filter() "},{"metadata":{"trusted":true,"_uuid":"1796757c36f9164d8d30279a75a3cfb7dc135b5e"},"cell_type":"code","source":"train_df.filter(regex='.*_.*', axis=1).head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"75f2523cbaacc05328b48424b7a769be94aaad5a"},"cell_type":"markdown","source":"#### handle customDimensions\n- data like [{'key1':'value1','key2':'value2'}] can be converted into dataframe by literal_eval\n- Same method can be used into column hits,but hits contains too much information,so we leave it alone "},{"metadata":{"trusted":true,"_uuid":"11b878ea21ae1dc7c54259eeb0a2b4cc3aa7df32"},"cell_type":"code","source":"train_df.customDimensions.head(1).values","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"30958fd555ce2d151d58c9ff71427ef24e340864"},"cell_type":"code","source":"def literal_col(data):\n    lst = literal_eval(data)\n    if isinstance(lst,list) and lst:\n        lst = lst[0]\n        return pd.Series(lst)\n    else:\n        return  pd.Series({})","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"6e5ff49f1abef52e9614fc09f0cb6208ac0a5913","_kg_hide-input":false,"_kg_hide-output":false},"cell_type":"code","source":"customer_cols = train_df.customDimensions.apply(literal_col)\nprint(customer_cols.head(1))\nprint(customer_cols.columns)","execution_count":null,"outputs":[]},{"metadata":{"scrolled":true,"trusted":true,"_uuid":"68e6a8c615e4d71a64f4f1aca81387279528543b"},"cell_type":"code","source":"customer_cols.columns = [f'customDimensions_{col}' for col in customer_cols.columns]\nprint(customer_cols.columns)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b4714f1d16cb60fc10526e5611f42fcee88d9f9b"},"cell_type":"code","source":"train_df = pd.concat([train_df,customer_cols],axis = 1)\ntrain_df.drop('customDimensions',axis = 1,inplace = True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"5b6b4e004427c057e59f1f725ae3ba3291575dc4"},"cell_type":"code","source":"train_df.filter(regex = 'customDimensions_.*',axis = 1)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9ed62344e65cb8ca4aae3e0faa213c314cfd2abe"},"cell_type":"markdown","source":"* #### we can delete constant columns\n-  nunique method  http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.nunique.html\n"},{"metadata":{"trusted":true,"_uuid":"cb563915984ca40c96523bee5c4793ef68fa169d"},"cell_type":"code","source":"constant_cols = [col for col in train_df.columns if train_df[col].nunique(dropna = False) == 1]\nprint(constant_cols)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"2098934b6d7323a1fd3a97778759dfa569bd9bb3"},"cell_type":"code","source":"train_df.drop(constant_cols,axis = 1,inplace = True)\ntrain_df.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a1d0a735ef113407fa67dd8b453c968e753f2540"},"cell_type":"code","source":"train_df.info()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"cabbc8f07270b80656334343299c06780da76fd5"},"cell_type":"code","source":"train_df['totals_transactionRevenue']=train_df['totals_transactionRevenue'].astype('float')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4af8cd3cc536a2dae720e3ef06546fc35d3d5461"},"cell_type":"code","source":"train_df['totals_transactionRevenue'].fillna(0,inplace = True)\ntrain_df['totals_transactionRevenue'].head(5)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"794f015973f307bc52356727e1ae4e06eab9cd40"},"cell_type":"code","source":"purchase_counts = train_df.loc[train_df['totals_transactionRevenue']>0,'fullVisitorId'].value_counts()\nprint(f'There are {len(purchase_counts)} users paid ({train_df.fullVisitorId.nunique()} users in total)')\nprint(f'There are {np.sum(purchase_counts > 1)} users paid more than once')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"315279a4a31d94647a743eb7ad4d5637d8d988d0"},"cell_type":"markdown","source":"####  visualization \n- plotly is a powerful visualization package，we can use it offline"},{"metadata":{"trusted":true,"_uuid":"454f563fcd2dc633f28472a30a892055e1d3749a"},"cell_type":"code","source":"def horizontal_bar_chart(df,color):\n    trace = go.Bar(\n        y=df.index[::-1],\n        x=df.values[::-1],\n        showlegend=False,\n        orientation = 'h',\n        marker=dict(\n            color=color,\n        ),\n    )\n    return trace","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d47e13eac2c1f0f982e7ac70eeca577f91ef8335"},"cell_type":"code","source":"# Device Browser\ncnt_srs = train_df.groupby('device_browser')['totals_transactionRevenue'].agg(['size', 'count', 'mean'])\ncnt_srs.columns = [\"count\", \"count of non-zero revenue\", \"mean\"]\ncnt_srs = cnt_srs.sort_values(by=\"count\", ascending=False)\ntrace1 = horizontal_bar_chart(cnt_srs[\"count\"].head(5), 'rgba(50, 171, 96, 0.6)')\ntrace2 = horizontal_bar_chart(cnt_srs[\"count of non-zero revenue\"].head(5), 'rgba(50, 171, 96, 0.6)')\ntrace3 = horizontal_bar_chart(cnt_srs[\"mean\"].head(5), 'rgba(50, 171, 96, 0.6)')\n\n# Device Category\ncnt_srs = train_df.groupby('device_deviceCategory')['totals_transactionRevenue'].agg(['size', 'count', 'mean'])\ncnt_srs.columns = [\"count\", \"count of non-zero revenue\", \"mean\"]\ncnt_srs = cnt_srs.sort_values(by=\"count\", ascending=False)\ntrace4 = horizontal_bar_chart(cnt_srs[\"count\"].head(5), 'rgba(71, 58, 131, 0.8)')\ntrace5 = horizontal_bar_chart(cnt_srs[\"count of non-zero revenue\"].head(5), 'rgba(71, 58, 131, 0.8)')\ntrace6 = horizontal_bar_chart(cnt_srs[\"mean\"].head(5), 'rgba(71, 58, 131, 0.8)')\n\n# Operating system\ncnt_srs = train_df.groupby('device_operatingSystem')['totals_transactionRevenue'].agg(['size', 'count', 'mean'])\ncnt_srs.columns = [\"count\", \"count of non-zero revenue\", \"mean\"]\ncnt_srs = cnt_srs.sort_values(by=\"count\", ascending=False)\ntrace7 = horizontal_bar_chart(cnt_srs[\"count\"].head(5), 'rgba(246, 78, 139, 0.6)')\ntrace8 = horizontal_bar_chart(cnt_srs[\"count of non-zero revenue\"].head(5),'rgba(246, 78, 139, 0.6)')\ntrace9 = horizontal_bar_chart(cnt_srs[\"mean\"].head(5),'rgba(246, 78, 139, 0.6)')\n\nfig = tools.make_subplots(rows=3, cols=3, vertical_spacing=0.04, \n                          subplot_titles=[\"Device Browser - Count\", \"Device Browser - Non-zero Revenue Count\", \"Device Browser - Mean Revenue\",\n                                          \"Device Category - Count\",  \"Device Category - Non-zero Revenue Count\", \"Device Category - Mean Revenue\", \n                                          \"Device OS - Count\", \"Device OS - Non-zero Revenue Count\", \"Device OS - Mean Revenue\"])\n\nfig.append_trace(trace1, 1, 1)\nfig.append_trace(trace2, 1, 2)\nfig.append_trace(trace3, 1, 3)\nfig.append_trace(trace4, 2, 1)\nfig.append_trace(trace5, 2, 2)\nfig.append_trace(trace6, 2, 3)\nfig.append_trace(trace7, 3, 1)\nfig.append_trace(trace8, 3, 2)\nfig.append_trace(trace9, 3, 3)\n\nfig['layout'].update(height=1200, width=1200)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e890178667030417bd9208b631887e9b9f02c49d"},"cell_type":"code","source":"py.iplot(fig, filename='device-plots')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"fb03513cb2201b2e5a771ecff2b4624859f4405c"},"cell_type":"markdown","source":"#### continue Feature Engineering\n- pd.to_datetime  convert Unix timestamp\n- pd.factorize  http://pandas.pydata.org/pandas-docs/stable/generated/pandas.factorize.html"},{"metadata":{"trusted":true,"_uuid":"d0fe39873fdc12eb82ceccdde0a671636d128f34"},"cell_type":"code","source":"train_df['date'] = pd.to_datetime(train_df['visitStartTime'], unit='s')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"aeda82b4310908d2980a8c8aeb47cc45e39a18ba"},"cell_type":"code","source":"def process_data(data_df):\n    data_df['totals_totalTransactionRevenue'] = data_df['totals_totalTransactionRevenue'].astype('float')\n    data_df['totals_pageviews']=train_df['totals_pageviews'].astype('float')\n    data_df['totals_hits']=train_df['totals_hits'].astype('float')\n    data_df['visitNumber'] = np.log1p(data_df['visitNumber'].fillna(0))\n    data_df['totals_hits'] = np.log1p(data_df['totals_hits'].fillna(0))\n    data_df['totals_pageviews'] = np.log1p(data_df['totals_pageviews'].fillna(0))\n    return data_df\ntrain_df = process_data(train_df) ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a0eca2b2421b64c9e0096340337ae80515ab857b"},"cell_type":"code","source":"print(train_df.columns)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"fb14d24057aeaeb893df43764e2a6df812aa1c5a"},"cell_type":"code","source":"excluded_features = [\n    'date','fullVisitorId', 'sessionId','totals_transactionRevenue',\n    'totals_totalTransactionRevenue','visitId', 'visitStartTime'\n]\ncategorical_features = [ _f for _f in train_df.columns if (_f not in excluded_features) & (train_df[_f].dtype == 'object')]\nfor f in categorical_features:\n    train_df[f],indexer = pd.factorize(train_df[f])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"138263569ebe296651d63640dea290ccdfc06802"},"cell_type":"markdown","source":"#### split train_df dataset into 3 parts"},{"metadata":{"trusted":true,"_uuid":"2dd0d4bf901f9b2f1b44a0ee7cd237c54ee731ac"},"cell_type":"code","source":"train_period_1 = train_df[(train_df['date']<=pd.datetime(2017,1,15)) & (train_df['date']>=pd.datetime(2016,8,1))]\ntrain_predict_preiod_1 = train_df[(train_df['date']<=pd.datetime(2017,4,30)) & (train_df['date']>=pd.datetime(2017,3,1))]\ntrain_period_2 = train_df[(train_df['date']<=pd.datetime(2017,11,15)) & (train_df['date']>=pd.datetime(2017,6,1))]\ntrain_predict_preiod_2 = train_df[(train_df['date']<=pd.datetime(2018,2,28)) & (train_df['date']>=pd.datetime(2018,1,1))]\nvalid_period = train_df[(train_df['date']<=pd.datetime(2017,10,15)) & (train_df['date']>=pd.datetime(2017,5,1))]\nvalid_predict_preiod = train_df[(train_df['date']<=pd.datetime(2018,1,31)) & (train_df['date']>=pd.datetime(2017,12,1))]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6f7f4f2978727e45be4c6ac988d566f93940a452"},"cell_type":"markdown","source":"#### create target feature"},{"metadata":{"trusted":true,"_uuid":"82ca8b21313a6b2512560ec0abc3eaf18c04d7aa"},"cell_type":"code","source":"def future_revenue(df):\n    period_future = df.groupby('fullVisitorId')['totals_transactionRevenue'].sum()\n    period_future = period_future.reset_index()\n    period_future.rename(columns = {'totals_transactionRevenue':'feature_revenue'},\n                         inplace = True)\n    period_future['feature_revenue'] = np.log1p(period_future['feature_revenue'])\n    return period_future","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"fe368cb2f585a9eaa20647656a8eeaf70f826744"},"cell_type":"code","source":"period1_future_revenue = future_revenue(train_predict_preiod_1)\nperiod2_future_revenue = future_revenue(train_predict_preiod_2)\nvalid_future_revenue = future_revenue(valid_predict_preiod)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"9bc4b791c8609558b891507888bb8135f1783359"},"cell_type":"code","source":"def fill(train_period,target_period):\n    train_period['totals_transactionRevenue'] = train_period['totals_transactionRevenue'].fillna(0).astype('float64')\n    target_period['totals_transactionRevenue'] = target_period['totals_transactionRevenue'].fillna(0).astype('float64')\n    revenue_train = train_period.groupby('fullVisitorId')['totals_transactionRevenue'].sum().values\n    revenue_target = target_period.groupby('fullVisitorId')['totals_transactionRevenue'].sum().values\n    target_pd = target_period.groupby('fullVisitorId').mean().reset_index()\n    target_pd['totals_transactionRevenue'] = revenue_target\n    train_pd = train_period.groupby('fullVisitorId').mean().reset_index()\n    train_pd['totals_transactionRevenue'] = revenue_train\n    train_visitors = train_pd['fullVisitorId'].unique()\n    train_predict_visitors = target_pd['fullVisitorId'].unique()\n    same_visitors = np.intersect1d(train_visitors, train_predict_visitors)\n    back_user = target_pd[(target_pd['fullVisitorId'].isin(same_visitors)) & (target_pd['totals_transactionRevenue'] > 0)]\n    back_user = back_user[['fullVisitorId','totals_transactionRevenue']]\n    print(f'numbers of back users is {len(same_visitors)}')\n    print('we have',len(back_user['fullVisitorId'].value_counts()),'visitors back to purchase at target periods')\n    \n    train_pd['totals_totalTransactionRevenue'] = np.log1p(train_pd['totals_totalTransactionRevenue'])\n    train_pd['totals_transactionRevenue'] = np.log1p(train_pd['totals_transactionRevenue'])\n    print (train_pd.shape)\n    return train_pd\n\ntrain_pd_1= fill(train_period_1,train_predict_preiod_1)#对应 train_period_1\ntrain_pd_2= fill(train_period_2,train_predict_preiod_2)#对应 train_period_2\nvalid_pd = fill(valid_period,valid_predict_preiod)#就是valid_period","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"054a204d69f5a81396e2eceee4b4e827c8b5376e"},"cell_type":"code","source":"train_pd_1['feature_revenue'] = 0\ntrain_pd_1 = pd.merge(train_pd_1,period1_future_revenue,\n                      on = 'fullVisitorId',\n                      how = 'left')\ntrain_pd_1.info()\ntrain_pd_1['feature_revenue_y'].fillna(0,inplace = True)\ntrain_pd_1.drop('feature_revenue_x',axis = 1,inplace = True)\ntrain_pd_1.rename(columns = {'feature_revenue_y':'feature_revenue'},\n                  inplace = True)\n\ntrain_pd_2['feature_revenue'] = 0\ntrain_pd_2 = pd.merge(train_pd_2,period2_future_revenue,\n                      on = 'fullVisitorId',\n                      how = 'left')\ntrain_pd_2.info()\ntrain_pd_2['feature_revenue_y'].fillna(0,inplace = True)\ntrain_pd_2.drop('feature_revenue_x',axis = 1,inplace = True)\ntrain_pd_2.rename(columns = {'feature_revenue_y':'feature_revenue'},\n                  inplace = True)\n\nvalid_pd['feature_revenue'] = 0\nvalid_pd = pd.merge(valid_pd,valid_future_revenue,\n                        on = 'fullVisitorId',\n                        how = 'left')\nvalid_pd.info()\nvalid_pd['feature_revenue_y'].fillna(0,inplace = True)\nvalid_pd.drop('feature_revenue_x',axis = 1,inplace = True)\nvalid_pd.rename(columns = {'feature_revenue_y':'feature_revenue'},\n                    inplace = True)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"84e196459e1479b55654b5570d16aeb40afbb405"},"cell_type":"code","source":"train_set = pd.concat([train_pd_1,train_pd_2], axis=0)\nexcluded_features = ['date','fullVisitorId','feature_revenue','visitId', 'visitStartTime']\ntrain_features = [_f for _f in train_set.columns if _f not in excluded_features ]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"83657f5ef6cd53a679778a98093c35447a88f3e8"},"cell_type":"markdown","source":"#### build lightgbm model"},{"metadata":{"trusted":true,"_uuid":"7ac79af949a21cc32731af96c97c2e34c6ef8f21"},"cell_type":"code","source":"y_target = train_set['feature_revenue']\nvalid_target = valid_pd['feature_revenue']\n\nparams = {\n        'boosting_type': 'gbdt', \n        'objective': 'regression',\n        'learning_rate': 0.01, \n        'num_leaves': 50, \n        'max_depth': 6,\n        'subsample': 0.8, \n        'colsample_bytree': 0.8, \n         }\ndata_train = lgb.Dataset(train_set[train_features],y_target,silent=True)\ncv_results = lgb.cv(params,data_train,\n                    num_boost_round=1000, \n                    nfold=5, \n                    stratified=False, \n                    shuffle=True, \n                    metrics='rmse',\n                    early_stopping_rounds=100, \n                    verbose_eval=50, \n                    show_stdv=True, seed=0)\nlen(cv_results['rmse-mean'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b9e8153b3806f1a18f9f5fcb4e9dd1b2a86e0cff"},"cell_type":"code","source":"model_lgb = lgb.LGBMRegressor(objective = 'regression',\n                              metric='rmse', \n                              learning_rate = 0.01,\n                              bagging_fraction = 0.8,\n                              feature_fraction = 0.8,\n                              n_estimators = 400)\nparam_lr ={'max_depth': [6,7,8],\n           'num_leaves':[50,100,150,200,250]}\n\ngsearch1 = GridSearchCV(estimator=model_lgb, \n                        param_grid=param_lr, \n                        scoring='neg_mean_squared_error', \n                        cv=5, \n                        verbose=1, \n                        n_jobs=4)\ngsearch1.fit(train_set[train_features],y_target)\n\n[gsearch1.grid_scores_, gsearch1.best_params_, gsearch1.best_score_]","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}