{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Introduction\n\nTailoring education to a student's ability level is one of the many valuable things an AI tutor can do.We will predict whether students are able to answer their next questions correctly.We will be provided with the same sorts of information a complete education app would have: that student's historic performance, the performance of other students on the same question, metadata about the question itself, and more.\n\nIn 2018, 260 million children weren't attending school. At the same time, more than half of these young students didn't meet minimum reading and math standards. Education was already in a tough place when COVID-19 forced most countries to temporarily close schools. This further delayed learning opportunities and intellectual development. The equity gaps in every country could grow wider. We need to re-think the current education system in terms of attendance, engagement, and individualized attention.\n\n### **This Notebook will contain Full Pipeline from data analytics to data modelling with powerful insights of prediction with explainable comments.**"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# Importing Libraries\nimport numpy as np # To deal with matrix calculations \nimport pandas as pd # This library will be used for for Structural Data Handling\nimport os # This Library is used for making interactions with the OS for File Handling\nimport matplotlib.pyplot as plt #For Data Visualization\nimport seaborn as sns # For Data Visualization\n%matplotlib inline \nsns.set() #for making background properties for visualization beautiful\nfrom scipy import stats # For Stastical Approach","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Data Structure and its analysis\n\nWe have been given three csv (Comma Seperated Values) files: **Train**, **Questions**, **Lectures**. All the three has been given with different features and related to each other\n* **Train**: This File Contains the **ID's related to Questions, User and Content**. Along with **Timestamps of Interaction of User with Content**.**Answers** Provided by the user and their overall **effeciency**.Now Content is Divided into two parts: As this Data has been collected from an **Educational Application**, they are divided into **Questions** and **Lectures**. Metadata related to Questions and Lectures has been provided in different files.\n\n* **Questions**: This File contain question ID's, their correct answers and tags to which these questions are related to.\n* **Lectures**:  This File contain Lecture ID along with Summary to what part is covered by this particular lecture.\n"},{"metadata":{},"cell_type":"markdown","source":"### Training File Overview\n\n**The Original Training File Contains Millions of Rows, which is not Memory effecient, So here we are taking first 1 lakh rows and in further in the notebook we will take the help of [Large DataFrame Import Technique Notebook](https://www.kaggle.com/rohanrao/tutorial-on-reading-large-datasets/), from which we will take Pickle technique.**"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# Train File . We will be using Pandas library \ntrain = pd.read_csv(\n    '/kaggle/input/riiid-test-answer-prediction/train.csv', \n    low_memory=False, \n    nrows=10**6, #We have the limit the rows for Memory Purposes\n    dtype={ # We are providing Data type as per specified in the problem statement\n        'row_id': 'int64', \n        'timestamp': 'int64', \n        'user_id': 'int32', \n        'content_id': 'int16', \n        'content_type_id': 'int8',\n        'task_container_id': 'int16', \n        'user_answer': 'int8', \n        'answered_correctly': 'int8', \n        'prior_question_elapsed_time': 'float32', \n        'prior_question_had_explanation': 'boolean'\n    }\n) # We have import the file into train variable\n\n#Let's See some Data Information\nprint(f'This File Contains {train.shape[0]} Records and {train.shape[1]} Features')\nprint('Below are the Top 5 Rows of the CSV file \\n')\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":false},"cell_type":"code","source":"print('Below are the Insights of the null values percentage in our Data')\nprint(train.isnull().sum()*100/train.shape[0])","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":false,"trusted":true},"cell_type":"code","source":"import plotly.express as px\nfrom plotly.subplots import make_subplots\nimport plotly.graph_objs as go\n\nds = train['user_id'].value_counts().reset_index() #We are counting unique User ID's and creating another dataframe named as ds\nds.columns = ['user_id', 'count'] # Naming Columns for the DataFrame as User_ID and Count\nds['user_id'] = ds['user_id'].astype(str) + '-' #This Has been done for Visualization as you can see in below graph on Y-axis User Id with an -\nds = ds.sort_values(['count']) # We are sorting in Ascending Order for Top Users Count\n\nfig = px.bar(\n    ds.tail(30), \n    x='count', # Specifying the X-axis\n    y='user_id', #Specifying the Y-axis\n    orientation='h', \n    title='Top 30 users by number of actions', \n    height=900, \n    width=700\n)\n\nfig.show()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Replacing NULL Values of prior_question_elapsed_time to 0 as according to the data description null values are given to that values which are 1st course in their bundles\ntrain['prior_question_elapsed_time'].fillna(0,inplace=True)\ntrain['prior_question_had_explanation'].fillna(False,inplace=True)\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":false,"trusted":true},"cell_type":"code","source":"answers = train['answered_correctly'].value_counts().reset_index() # Creating DataFrame for Answers whether correct, incorrect or lectures \nanswers.columns = ['answers','count']\n\n#Here in Pie Chart 1 represents Answered Correctly, 0 represents Answered Incorrect and -1 represents Lectures\nfig = px.pie(answers,values='count',names='answers', title='Showing relative percentage of Answers')\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":false,"trusted":true},"cell_type":"code","source":"answers = train['prior_question_had_explanation'].value_counts().reset_index() # Creating DataFrame for Answers whether correct, incorrect or lectures \nanswers.columns = ['explaination','count']\n\n#Here in Pie Chart 1 represents Answered Correctly, 0 represents Answered Incorrect and -1 represents Lectures\nfig = px.pie(answers,values='count',names='explaination', title='Showing relative percentage of Explanations presence for prior question')\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":false,"trusted":true},"cell_type":"code","source":"fig = px.histogram(train,x='prior_question_elapsed_time',nbins=300)\nfig.show()\nprint('In Above Figure we can see that the graph is skewed to left, means Most of the elapsed time is before 50000 milliseconds. This is Skewed Graph we will normalize it .')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Questions File Overview"},{"metadata":{"trusted":true},"cell_type":"code","source":"questions = pd.read_csv('/kaggle/input/riiid-test-answer-prediction/questions.csv')\nquestions","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"questions['tag'] = questions['tags'].str.split(' ') #Splitiing Difeerent Tags so we can work with them\nquestions = questions.explode('tag') # Explode Function is used to make multiple values in a single row into single value row by duplicating the rows and different column values. e.g:- Tags here\nquestions = pd.merge(questions, questions.groupby('question_id')['tag'].count().reset_index(), on='question_id') #merge function\nquestions = questions.drop(['tag_x'], axis=1) # we are dropping the column from the dataframe\nquestions.columns = ['question_id', 'bundle_id', 'correct_answer', 'part', 'tags', 'tags_number']\nquestions = questions.drop_duplicates() # Removal of Duplicates\nquestions","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# Comparing the correct answers percentages given by Users and Actual Correct answers\n\nactual_correct = questions['correct_answer'].value_counts().reset_index()\nactual_correct.columns = ['correct_answers','count']\n\nuser_answers = train['user_answer'].value_counts().reset_index()\nuser_answers.columns = ['user_answers','count']\n\nfig1 = px.pie(actual_correct,values='count',names='correct_answers',title='Actual Answers Relative Percentage')\n\nfig2 = px.pie(user_answers[user_answers['user_answers'] != -1],values='count',names='user_answers',title='User Answers Relative Proportions')\n\nfig1.show()\nfig2.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# Top Most Tags \ndistinct_tags = set() # A Set for Collecting Different Tags\ntags = questions['tags'].astype(str) # Converting Data Type for Tags Column\n# Collecting Tags\nfor i in tags:\n    for t in i.split(' '):\n        distinct_tags.add(t)\ndistinct_tags = list(distinct_tags)\nn = []\n#Collecting Different Counts for Different Tags\nfor tag in distinct_tags:\n    count = 0\n    for t in tags:\n        if tag in t:\n            count += 1\n    n.append(count)\n# Tags DataFrame \ntags_df = pd.DataFrame({'Tags':distinct_tags,'Count':n})\ntags_df = tags_df.sort_values(['Count'])\ntags_df['Tags'] = tags_df['Tags'].astype(str) + '-'\n#Figure Creation\nfig = px.bar(\n    tags_df.tail(20), \n    x='Tags', # Specifying the X-axis\n    y='Count', #Specifying the Y-axis\n    title='Most Useful Contents', \n    height=900, \n    width=700\n)\n\nfig.show()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Lectures File Overview"},{"metadata":{"trusted":true},"cell_type":"code","source":"lectures = pd.read_csv('../input/riiid-test-answer-prediction/lectures.csv')\nlectures","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# Different type of Lectures : Concept or Solving Question\ntype_lec = lectures['type_of'].value_counts().reset_index()\ntype_lec.columns = ['Type','Count']\n\nfig = px.bar(type_lec,x='Type',y='Count',title='Relative Type of Lectures',color=['Concept','Solving Question','Intention','Starter'])\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#freeing up some memory\ndel train\ndel questions\ndel lectures","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Feature Extraction and Engineering\n\nThe **Most Important** process for Data Modeling is **Feature Extraction and Engineering**. As we all know, Machine Learning depends mostly on Data and its features, we are done with data analytics and now will be moving on to Feature creation and extraction. In this Section, we will be going to explore various feature creation and processing techniques.\n\nFirst, we going to explore the **example_test.csv** file, through which we have have to create our feature pipeline later for Model preprocessing.\n**As per the Data Description**:  Three sample groups of the test set data as it will be delivered by the time-series API. The format is largely the same as train.csv. There are two different columns that mirror what information the AI tutor actually has available at any given time, but with the user interactions grouped together for the sake of API performance rather than strictly showing information for a single user at a time. Some users will appear in the hidden test set that have NOT been presented in the train set, emulating the challenge of quickly adapting to modeling new arrivals to a website.\n\n* **Steps**:\n    1. Examining the Test Data Sample.     \n    2. Creating a New Train Dataset for Model with important Features.\n    3. Filter Train Data for Questions and Remove rows for Lecture based.\n    4. Grouping and Dividing Data on Basis of User ID and Content ID.\n    5. Creating New Features on Both DataSets.\n    6. Merging Two DataSets and New and Insightful Data Formation and Rearranging Columns (Features and Target)\n    7. Data Wrangling (Missing Values Treatment)"},{"metadata":{},"cell_type":"markdown","source":"### Step-1: Examining the Test Data Sample"},{"metadata":{"trusted":true},"cell_type":"code","source":"example_test = pd.read_csv('../input/riiid-test-answer-prediction/example_test.csv')\nexample_test\n#Example Test Set is Similar to Train DataSet, unlike two more columns prior_group_answers_correct, prior_group_responses","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Step-2: Creating a New Train Dataset for Model with important Features.\n\n**Now will take Large Dataset into handle using pickle file of Training DataSet Uploaded with this Notebook**"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Determing Features and their DataType for Train Data\nused_features_dict = {\n    'timestamp': 'int64',\n    'user_id': 'int32',\n    'content_id': 'int16',\n    'answered_correctly': 'int8', # This will be the Target Feature. Here we have been doing classification problem between 0 and 1\n    'prior_question_elapsed_time': 'float16',\n    'prior_question_had_explanation': 'boolean'\n}\n\n# Creating New DataFrame for Training Purpose\ntrain = pd.read_pickle('../input/riiid-train-data-multiple-formats/riiid_train.pkl.gzip')\ntrain_df = train[:5000000]\ndel train\nprint(f'DataFrame Shape: {train_df.shape}')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Step-3: Filter Train Data for Questions and Remove rows for Lecture based.\n\nAs per the Data Description, any value in answered_correctly column and many other columns equal to -1 will be corressponding to Lectures"},{"metadata":{"trusted":true},"cell_type":"code","source":"# We will be given condition for filtering out\ntrain_df = train_df[used_features_dict.keys()]\ntrain_questions_only_df = train_df[train_df['answered_correctly']!=-1]\ntrain_questions_only_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Step-4: Grouping and Dividing Data on Basis of User ID and Content ID."},{"metadata":{"trusted":true},"cell_type":"code","source":"user_df = train_questions_only_df.groupby('user_id') #Group By Function will group by user-id\ncontent_df = train_questions_only_df.groupby('content_id')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Step-5: Creating New Features on Both DataSets.\n\nNow we have to engineer the features. What we can think of Making Statistical Features like Mean, Median, Standard Deviation and Skewness\nFirst We create features and then merge both of them main dataframe using UserID and ContentID like we use to do in SQL Inner Join.In the next step we will be Using **Left Join** concept"},{"metadata":{"trusted":true},"cell_type":"code","source":"user_answers_df = user_df.agg({'answered_correctly': ['mean', 'count', 'std', 'skew']}).copy() #Using AGG feature means Aggregate Functions like in SQL.\nuser_answers_df.columns = ['mean_user_accuracy', 'questions_answered', 'std_user_accuracy', 'skew_user_accuracy']\n\nuser_answers_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"del user_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"content_answers_df = content_df.agg({'answered_correctly': ['mean', 'count', 'std', 'skew'] }).copy()\ncontent_answers_df.columns = ['mean_accuracy', 'question_asked', 'std_accuracy', 'skew_accuracy']\n\ncontent_answers_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# We can delete old dataframes to free up memory. It is a Good Practice.\ndel content_df","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Step-6: Merging Two DataSets and New and Insightful Data Formation and rearranging columns."},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = train_df.merge(user_answers_df, how='left', on='user_id')\ntrain_df = train_df.merge(content_answers_df, how='left', on='content_id')\ntrain_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"features = ['user_id', 'content_id',\n       'prior_question_elapsed_time', 'prior_question_had_explanation',\n       'mean_user_accuracy', 'questions_answered', 'std_user_accuracy',\n        'skew_user_accuracy', 'mean_accuracy',\n       'question_asked', 'std_accuracy',  'skew_accuracy']\ntarget = ['answered_correctly']\n\nfrom sklearn.preprocessing import LabelEncoder\ntrain_df = train_df[features + target]\n\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(train_df.isnull().sum())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Step-7: Data Wrangling (Missing Values Treatment)"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df['prior_question_had_explanation'] = train_df['prior_question_had_explanation'].fillna(value = False).astype(bool)\ntrain_df = train_df.fillna(value = 0.5) #Filling Values with 0.5","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = train_df.replace([np.inf, -np.inf], np.nan)\ntrain_df = train_df.fillna(0.5)\ntrain_df = train_df[train_df['answered_correctly'] != -1]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"lb = LabelEncoder()\ntrain_df['prior_question_had_explanation'] = lb.fit_transform(train_df['prior_question_had_explanation'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.preprocessing import StandardScaler\nsc = StandardScaler()\nX = train_df[features]\nY = train_df[target]\nX = sc.fit_transform(X)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Modelling\n\nModelling is a part of the pipeline which actually learns the rule of the data they follow and make prediction according to it. The very first and crucial step in Modelling is **Splitting the Data into Train and Test Sets**. Then we will use hyperparameter tuning for our Model. There are many Classification Model available. We will going to use XGBoost and LGBMClassifier."},{"metadata":{"trusted":true},"cell_type":"code","source":"# Libraries Importation for Classification\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, roc_auc_score\nimport optuna\nfrom optuna.samplers import TPESampler","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Train and Test Dataset Spliting"},{"metadata":{"trusted":true},"cell_type":"code","source":"X_train,X_test,Y_train,Y_test = train_test_split(X,Y, random_state=666, test_size=0.2,shuffle=True) # Going to split in 80% and 20% part\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_lstm_train = X_train\nX_cnn_train = X_train\nX_lstm_test = X_test\nX_cnn_test  =X_test","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"Y_lstm_train = Y_train\nY_cnn_train = Y_train\nY_lstm_test = Y_test\nY_cnn_test  =Y_test","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## DEEP LEARNING "},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.models import Sequential\nfrom keras.layers import Dense, Activation, Dropout,Conv1D,Flatten,LSTM,Embedding\nimport tensorflow as tf\n\nX_lstm_train = X_lstm_train.reshape(X_lstm_train.shape[0],1, X_lstm_train.shape[1])\nX_lstm_test = X_lstm_test.reshape(X_lstm_test.shape[0],1, X_lstm_test.shape[1])\nprint(X_lstm_train.shape)\ndef create_lstm_model():\n    model=Sequential()\n    model.add(LSTM(50,input_shape=(1,X_lstm_train.shape[2]),return_sequences=True))\n    model.add(LSTM(150))\n    model.add(Dense(64, activation='relu'))\n    model.add(Dense(128, activation='relu'))\n    model.add(Flatten())\n    model.add(Dense(256,activation='relu'))\n    model.add(Dense(256,activation='relu'))\n    model.add(Dense(1, activation='sigmoid'))\n\n    \n    model.compile(optimizer='adam', loss='binary_crossentropy', metrics=[tf.keras.metrics.AUC()])\n    print(model.summary())\n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.models import Sequential\nfrom keras.layers import Dense, Activation, Dropout,Conv1D,Flatten\nimport tensorflow as tf\n\n\nX_cnn_train = X_cnn_train.reshape(X_cnn_train.shape[0], X_cnn_train.shape[1], 1)\nX_cnn_test = X_cnn_test.reshape(X_cnn_test.shape[0], X_cnn_test.shape[1], 1)\ndef create_model():\n    model=Sequential()\n    model.add(Conv1D(64, 2, activation='relu', input_shape=X_cnn_train.shape[0]))\n    model.add(Conv1D(128, 2, activation='relu'))\n\n    model.add(Dropout(0.5))\n    model.add(Dense(64, activation='relu'))\n    model.add(Dropout(0.5))\n    model.add(Dense(128, activation='relu'))\n    model.add(Dropout(0.5))\n    model.add(Flatten())\n    model.add(Dense(256,activation='relu'))\n    model.add(Dense(1, activation='sigmoid'))\n\n    model.compile(optimizer='adam', loss='binary_crossentropy', metrics=[tf.keras.metrics.AUC()])\n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\n\nfrom keras.callbacks import EarlyStopping\nes = EarlyStopping(monitor='val_auc_1', mode='max',patience=5)\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"'''model = create_lstm_model()\nhistory = model.fit(X_lstm_train,Y_lstm_train, epochs=150, verbose=1, validation_data=(X_lstm_test,Y_lstm_test),batch_size=65536,callbacks=[es])\nmodel.save('model.h5')'''","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Test Results Generation"},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.models import load_model\nmodel = load_model('../input/modelforriiid/model(3).h5')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import riiideducation\n\n#Creating Environment\nenv = riiideducation.make_env()\n\niter_test = env.iter_test()\n\nfor (test_df, sample_prediction_df) in iter_test:\n    test_df = test_df.merge(user_answers_df, how = 'left', on = 'user_id')\n    test_df = test_df.merge(content_answers_df, how = 'left', on = 'content_id')\n    test_df['prior_question_had_explanation'] = test_df['prior_question_had_explanation'].fillna(value = False).astype(bool)\n    test_df = test_df.replace([np.inf, -np.inf], np.nan)\n    test_df.fillna(value = 0.5, inplace = True)\n    lb = LabelEncoder()\n    test_df['prior_question_had_explanation'] = lb.fit_transform(test_df['prior_question_had_explanation'])\n    '''test_df['prior_question_elapsed_time'] = test_df['prior_question_elapsed_time'].abs()\n    test_df.prior_question_elapsed_time.replace(0,0.5,inplace=True)\n    test_df['prior_question_elapsed_time'] = stats.boxcox(test_df['prior_question_elapsed_time'])[0]'''\n    sc = StandardScaler()\n    X_test = test_df[features]\n    X_test = sc.fit_transform(X_test)\n    X_test = X_test.reshape(X_test.shape[0],1,X_test.shape[1])\n    test_df['answered_correctly'] = model.predict_proba(X_test)\n    env.predict(test_df.loc[test_df['content_type_id'] == 0, ['row_id', 'answered_correctly']])\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# WORK IN PROGRESS"},{"metadata":{"trusted":true},"cell_type":"code","source":"df = pd.read_csv('./submission.csv')\ndf","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}