{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Project 11: Building Energy Consumption\n*Xinchang Li <br>\nOctober 14, 2020*\n## 1. Exploratory Data Analysis\n### 1.1 Introduction\nThis notebook covers the exploratory data analysis (EDA) on the ASHRAE Great Energy Predictor III dataset. The competition challenges participants to accurately model four types of hourly metered building energy usage: chilled water, electric, hot water, and steam, based on historic usage rates and observed weather. The data include over 1,000 buildings across multiple sites over a three-year timeframe, and are pre-partitioned into training and testing data. "},{"metadata":{},"cell_type":"markdown","source":"### 1.2 Data and Modules Imports\nThe following cells import useful modules and reads the data (in .csv format) in from the Kaggle server."},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# Load useful modules\nimport os\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport matplotlib.pyplot as plt\nimport seaborn as sns; sns.set()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# Print all files in the input directory (auto-generated code from Kaggle)\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Read in the .csv files as Pandas DataFrame (this can take around 2 minutes)\nbldg_meta = pd.read_csv('/kaggle/input/ashrae-energy-prediction/building_metadata.csv')\n\ntrain = pd.read_csv('/kaggle/input/ashrae-energy-prediction/train.csv')\nweather_train = pd.read_csv('/kaggle/input/ashrae-energy-prediction/weather_train.csv')\n\ntest = pd.read_csv('/kaggle/input/ashrae-energy-prediction/test.csv')\nweather_test = pd.read_csv('/kaggle/input/ashrae-energy-prediction/weather_test.csv')\n\nsample_sub = pd.read_csv('/kaggle/input/ashrae-energy-prediction/sample_submission.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Plots formatter (borrowed from https://stackoverflow.com/questions/3899980/how-to-change-the-font-size-on-a-matplotlib-plot)\nSMALL_SIZE = 10\nMEDIUM_SIZE = 12\nBIGGER_SIZE = 16\n\nplt.rc('font', size=SMALL_SIZE)          # controls default text sizes\nplt.rc('axes', titlesize=BIGGER_SIZE)    # fontsize of the axes title\nplt.rc('axes', labelsize=MEDIUM_SIZE)    # fontsize of the x and y labels\nplt.rc('xtick', labelsize=SMALL_SIZE)    # fontsize of the tick labels\nplt.rc('ytick', labelsize=SMALL_SIZE)    # fontsize of the tick labels\nplt.rc('legend', fontsize=SMALL_SIZE)    # legend fontsize\nplt.rc('figure', titlesize=BIGGER_SIZE)  # fontsize of the figure title","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 1.3 Basic Information: Building Meta Data\nIn this section we will try and answer the following questions with the builing metadata, `bldg_meta`: How many samples are in the DataFrame? What are the feature variables, and how are they distributed?"},{"metadata":{"trusted":true},"cell_type":"code","source":"N, d = bldg_meta.shape\nprint(f'In bldg_meta, there are {N} samples and {d} features.')\nfeatures = bldg_meta.columns\nprint(f'The {d} features are: ', list(features))\nprint('Here are the first 10 rows of the DataFrame:')\nbldg_meta.head(10)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"There are in total 16 sites, labeled 0~15 (`site_id`), containing 1449 buildings. Each building is identified with a unique `building_id` independent from the `site_id`, from 0 to 1449, as tested in the following cell."},{"metadata":{"trusted":true},"cell_type":"code","source":"assert np.array_equal(bldg_meta.site_id.unique(), np.arange(0, 16))\nassert np.array_equal(bldg_meta.building_id.unique(), np.arange(0, 1449))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We will note that over 75% of the buildings are missing `floor_count` data, and over 50% are missing `year_built`, as shown below. This may hinder their use as features for model training and prediction. "},{"metadata":{"trusted":true},"cell_type":"code","source":"print('Missing data')\nfor col in bldg_meta['primary_use year_built square_feet floor_count'.split()].columns:\n    missing = pd.isnull(bldg_meta[col]).sum() # returns the total no. of empty entries in the column\n    pct = round(missing/N*100, 1) # N is the total no. of entries = total no. of buildings\n    print(f'\\t{col}: \\t{missing} ({pct}%)')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We will visualize the number of buildings in each site with a histogram below. "},{"metadata":{"trusted":true},"cell_type":"code","source":"# Set up subplot\nfig, ax = plt.subplots(1, 1, figsize=(10, 5))\nn, bins, patches = ax.hist(bldg_meta.site_id, bins=15)\n\n# Annotate each bar with the no. of buildings in that site:\nfor number, b in zip(n, bins[:-1]):\n    ax.annotate(int(number), \n                 xy=(b+.5, number), xytext=(0, 1),#1 point vertical offset\n                 textcoords=\"offset points\",\n                 ha='center', va='bottom', fontsize=12)\n# Set x ticks at the center of each bar\nax.set_xticks(np.arange(0.5, 15., step=1))\nax.set_xticklabels(np.arange(0, 16))\n# Set y limits\nax.set_ylim([0, 300])\n# Set x and y labels and add a title\nax.set_xlabel('site_id')\nax.set_ylabel('number of buildings')\nax.set_title('Number of buildings in each site', fontsize=16);","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Next, we will look at the distributions of the rest of the features in `bldg_meta`: `primary_use`, `year_built`, `square_feet`, and `floor_count`, "},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, axes = plt.subplots(1, 4, figsize=(20, 5))\nfor i, col in enumerate('primary_use year_built square_feet floor_count'.split()):\n    bldg_meta[col].hist(xrot=90, ax=axes[i], bins=min(25, len(bldg_meta[col].unique())))\n    axes[i].set_title(col)\nfig.suptitle('Distributions of bldg_meta features');","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"There are 16 primary use types, with a mix of residential and commercial buildings, mostly built after the 1950s. Both the building square footage and floor counts are approximately logarithmically distributed, meaning most buildings are relatively small, single- to multi-story buildings. We can also compute the correlations between the features below. There's a fairly strong correlation between building square footage and floor counts, as we could expect, and a small but positive correlation between the constructed year and the building size. "},{"metadata":{"trusted":true},"cell_type":"code","source":"bldg_meta['year_built square_feet floor_count'.split()].corr()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 1.4 Training data\nNow, onto `train`. This DataFrame includes 20,216,100 hourly meter readings, which is our target variable, identified by `building_id`, `meter`, and `timestamp`. `building_id` contains the same values from `bldg_meta`, providing base for joining the two DataFrames. The `meter` column contains meter ID code with the following mapping as describe in the [competition](https://www.kaggle.com/c/ashrae-energy-prediction/data): \n> `{0: electricity, 1: chilledwater, 2: steam, 3: hotwater}`"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"print('train shape: ', train.shape)\nprint('Missing meter_reading: ', end='')\n\nmissing = pd.isnull(train['meter_reading']).sum() # returns the total no. of empty entries in the column\npct = round(missing/N*100, 1) # N is the total no. of entries = total no. of buildings\nprint(f'\\t{missing} ({pct}%)')\n\ntrain.head(10)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### 1.4.1 Total Energy Use\nAlthough we are to predict the load profile, let's take a look at the total energy use (sum of all meter readings for each building), and its correlations with other features from `bldg_meta`, to see if it may guide our feature selections for model training."},{"metadata":{"trusted":true},"cell_type":"code","source":"# Compute the sum of meter readings\ntot_meter_per_bldg = train[['building_id', 'meter_reading']].groupby('building_id').sum()\n# Join with bldg_meta based on building_id\ntot_meter_per_bldg = bldg_meta.merge(tot_meter_per_bldg, on='building_id')\ntot_meter_per_bldg.rename(columns={'meter_reading': 'tot_meter_reading'}, inplace=True)\ntot_meter_per_bldg.head(10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Make the plot\ntot_meter_per_bldg.tot_meter_reading.plot(logy=True, style='.', \n                                          title='Sum of meter readings per building in the training set', \n                                          xlabel='building_id', ylabel='total meter reading',\n                                          figsize=(10, 5));","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We notice that most buildings have a total meter reading between $10^4$ to $10^8$, but the minimum and maximum are rather far away from the rest. We will extract their building ID and note them down. "},{"metadata":{"trusted":true},"cell_type":"code","source":"min_id = tot_meter_per_bldg.tot_meter_reading.sort_values(ascending=True).index[0]\nmax_id = tot_meter_per_bldg.tot_meter_reading.sort_values(ascending=True).index[-1]\ntot_meter_per_bldg[(tot_meter_per_bldg.index==min_id)|(tot_meter_per_bldg.index==max_id)]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"After removing the outliners, we will compute the correlations between total meter readings and other features from `bldg_meta`. Total meter readings exhibit fairly signficant positive correlation with building square footage, and smaller but also positive correlations with floor counts and construction year."},{"metadata":{"trusted":true},"cell_type":"code","source":"tot_meter_per_bldg[(tot_meter_per_bldg.index!=min_id)\n                   &(tot_meter_per_bldg.index!=max_id)][['square_feet', 'year_built', 'floor_count', 'tot_meter_reading']].corr()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### 1.4.2 Target variable time series profiles"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Join train with bldg_meta based on building_id\ntrain_meta = bldg_meta.merge(train, on='building_id')\ntrain_meta['timestamp'] = pd.to_datetime(train_meta.timestamp)\ntrain_meta.head(10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get a list of primary uses and its length\nprim_use_list = train_meta['primary_use'].unique()\nlen(prim_use_list)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Group by primary use and plot time series profiles\nfig, axes = plt.subplots(8, 2, figsize=(20, 35))\n\n# For education buildings, we will drop the two outliers we identified earlier for now; explanation comes later. \nedu_df = train_meta[(train_meta['primary_use']=='Education')&(train_meta['building_id']!=min_id)&(train_meta['building_id']!=max_id)]\n# Daily energy use for each building\nedu_daily = edu_df.groupby(['building_id', edu_df['timestamp'].dt.date])['meter_reading'].sum()\nedu_daily = edu_daily.reset_index()\nedu_mean = edu_daily.groupby('timestamp')['meter_reading'].mean()\naxes[0, 0].plot(edu_mean.index, edu_mean)\naxes[0, 0].set_title('Education (excl. 1099 & 740)')\n\n# For the rest of the building types we will write a loop for batch ploting:\nfor ax, use in zip(axes.flat[1:], prim_use_list[1:]): \n    prim_use_df = train_meta[train_meta['primary_use']==use]\n    prim_use_daily = prim_use_df.groupby(['building_id', prim_use_df['timestamp'].dt.date])['meter_reading'].sum()\n    prim_use_daily = prim_use_daily.reset_index()\n    mean = prim_use_daily.groupby('timestamp')['meter_reading'].mean()\n    \n    ax.plot(mean.index, mean)\n    ax.set_title(use)\n\n# The following lines help create common X and Y labels.\n# Borrowed from: https://stackoverflow.com/questions/6963035/pyplot-axes-labels-for-subplots/36542971#36542971\nfig.add_subplot(111, frameon=False)\n# hide tick and tick label of the big axes\nplt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)\nplt.grid(False)\nplt.xlabel('Time')\nplt.ylabel('Meter Reading (Daily Sum)', labelpad=20)\n\nplt.title('Time series profiles for different building types', pad=30)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"From the annual load profiles for the average daily energy use, we can see that Education, Lodging/Residential, Office, Public Services, Warehouse/Storage and Healthcare, where heating, ventilation and air conditioning (HVAC) are important to maintain indoor temperatures,  tend to have two peaks in their annual profiles (albeit of different patterns and magnitudes), one around June-July-August (JJA) and another around December-Janurary-Feburary (DJF). These coincide with the summer and winter seasons (of the Northern Hemisphere; and the other way around for the Southern Hemisphere), respectively. This is suggestive that weather data will play an important role in prediciting load profiles for these buildings, and 'month of year' could be a useful engineered feature.<br>"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_meta['month'] = train_meta['timestamp'].dt.month","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now we will explain why we excluded the previously identified outliers: 1099 and 740, both of which happen to be educational builidngs. We will plot their profiles to see if there's anything strange about them:"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Outliers: \nmax_edu_daily = train_meta[train_meta['building_id']==max_id].groupby(train_meta['timestamp'].dt.date)['meter_reading'].sum()\nmin_edu_daily = train_meta[train_meta['building_id']==min_id].groupby(train_meta['timestamp'].dt.date)['meter_reading'].sum()\n\nfig, axes = plt.subplots(1, 2, figsize=(18, 4))\naxes[0].plot(max_edu_daily.index, max_edu_daily)\naxes[0].set_title(f'maximum consumption ({max_id})')\naxes[1].plot(min_edu_daily.index, min_edu_daily)\naxes[1].set_title(f'minimum consumption ({min_id})')\n\nfig.add_subplot(111, frameon=False)\n# hide tick and tick label of the big axes\nplt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)\nplt.grid(False)\nplt.xlabel('Time')\nplt.ylabel('Meter Reading (Daily Sum)', labelpad=20)\n\nplt.title('Time series profiles for the maximum- and minimum- consumption building', pad=30)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"It is clear that both of these buildings do not conform to the average Education load profiles. Building 1099 would have dominated the shape of the entire profile, owning to its orders-of-magnitude larger values, and the spike in November would have been preserved too. Building 740 on the other hand has near-zero, perfectly flat energy use throughout the year (note the y-axis values; these numbers can well be the measuring equipment bias!). We will therefore drop these two buildings for the following discussion and when training the model. "},{"metadata":{"trusted":true},"cell_type":"code","source":"train_meta_no_outlier = train_meta[(train_meta['building_id']!=min_id)&(train_meta['building_id']!=max_id)]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### 1.4.3 Meter time series profiles\nWe will start with making the average daily profiles for different meters."},{"metadata":{"trusted":true},"cell_type":"code","source":"meters = {0: 'electricity', 1: 'chilledwater', 2: 'steam', 3: 'hotwater'}\n\nfig, axes = plt.subplots(2, 2, figsize=(20, 10))\n\nfor ax, m in zip(axes.flat, meters): \n    meter_df = train_meta_no_outlier[train_meta_no_outlier['meter']==m]\n    meter_daily = meter_df.groupby(['meter', meter_df['timestamp'].dt.date])['meter_reading'].sum()\n    meter_daily = meter_daily.reset_index()\n    mean = meter_daily.groupby('timestamp')['meter_reading'].mean()\n    \n    ax.plot(mean.index, mean)\n    ax.set_title(meters[m])\n\n# The following lines help create common X and Y labels.\n# Borrowed from: https://stackoverflow.com/questions/6963035/pyplot-axes-labels-for-subplots/36542971#36542971\nfig.add_subplot(111, frameon=False)\n# hide tick and tick label of the big axes\nplt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)\nplt.grid(False)\nplt.xlabel('Time')\nplt.ylabel('Meter Reading (Daily Sum)', labelpad=20)\n\nplt.title('Time series profiles for different meter types', pad=30)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"As we can expect, electricity and steam uses peak during JJA, as they are mostly associated with the use of spacing cooling. Similarly, steam and hot water peak during DJF due to space heating. <br>\nAnother interesting feature we note is the repeated weekly pattern in electricity use. This suggests that 'day of week' could also be a useful engineered feature."},{"metadata":{"trusted":true},"cell_type":"code","source":"train_meta_no_outlier['day_of_week'] = train_meta_no_outlier['timestamp'].dt.weekday","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Visualize weekly profiles\nfig, axes = plt.subplots(2, 2, figsize=(10, 10))\nfor ax, m in zip(axes.flat, meters): \n    mean = train_meta_no_outlier[train_meta_no_outlier['meter']==m].groupby('day_of_week')['meter_reading'].mean()\n    \n    ax.plot(mean.index, mean)\n    ax.set_title(meters[m])\n    ax.set_xticks(np.arange(0, 7))\n    ax.set_xticklabels(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'])\n\nfig.add_subplot(111, frameon=False)\n# hide tick and tick label of the big axes\nplt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)\nplt.grid(False)\nplt.xlabel('Weekday')\nplt.ylabel('Meter Reading (Average Daily Sum )', labelpad=20)\n\nplt.title('Time series profiles for different meter types', pad=30)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### 1.4.4 Site time series profiles\nWe are also interested in how what the energy consumption for each site looks like. "},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load profiles by site_id\nsite_daily_sum = train_meta_no_outlier.groupby(['site_id', train_meta_no_outlier.timestamp.dt.date])['meter_reading'].sum().reset_index()\n\nfig, axes = plt.subplots(8, 2, figsize=(20, 35), tight_layout=True)\nfor i, ax in zip(range(16), axes.flat):\n    site_daily_sum[site_daily_sum.site_id == i][['timestamp', 'meter_reading']].plot(ax = ax, x = 'timestamp', legend=False);\n    ax.set_title(f'site {i}')\n    \nfig.add_subplot(111, frameon=False)\n# hide tick and tick label of the big axes\nplt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)\nplt.grid(False)\nplt.xlabel('Time')\nplt.ylabel('Meter Reading (Average Daily Sum )', labelpad=20)\n\nplt.title('Time series profiles for different meter types', pad=30)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"As we can see in the figure above, Site 0 had zero meter reading until March 2016. It could be that the site did not come into use until the time. This means that we might need to discard the zeros when training our models. The same may be true to Site 15, where between Feburary and March there is zero meter readings for about a month."},{"metadata":{},"cell_type":"markdown","source":"### 1.5 Weather Data\n`weather_train` has 2016 hourly weather data, and `weather_test` has 2017~18 hourly weather data."},{"metadata":{"trusted":true},"cell_type":"code","source":"weather_train['timestamp'] = pd.to_datetime(weather_train.timestamp)\nweather_test['timestamp'] = pd.to_datetime(weather_test.timestamp)\n\nprint(f'weather_train: {weather_train.shape}')\ndisplay(weather_train.head())\nprint(f'weather_test: {weather_test.shape}')\ndisplay(weather_test.head())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def weather_missing(df, df_name):\n    N = df.shape[0]\n    print(f'Missing data in {df_name}:')\n    for col in df.columns[2:]:\n        missing = pd.isnull(df[col]).sum() # returns the total no. of empty entries in the column\n        pct = round(missing/N*100, 1) # N is the total no. of entries\n        print(f'\\t{col}: \\t{missing} ({pct}%)')\n        \nweather_missing(weather_train, 'weather_train')\nprint('')\nweather_missing(weather_test, 'weather_test')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We will visualize the time series of both the training and testing weather data below."},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, axes = plt.subplots(7, 1, sharex=True, figsize=(20, 25))\n\nfor col, ax in zip(weather_train.columns[2:], axes):\n    weather_train[['timestamp', col]].set_index('timestamp').resample('D').mean()[col].plot(ax=ax, color='tab:blue', label='train')\n    weather_test[['timestamp', col]].set_index('timestamp').resample('D').mean()[col].plot(ax=ax, color='tab:orange', label='test')\n    ax.set_ylabel(col)\n\naxes[0].set_title('Weather data time series')\naxes[-1].set_xlabel('Time')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 1.5 Putting things together\nWe have looked at each dataset seperately, now we will join the `train`, `weather_train` and `bldg_meta` to see if there are any correlations between these variables."},{"metadata":{"trusted":true},"cell_type":"code","source":"train_meta_weather_no_outlier = train_meta_no_outlier.merge(weather_train, on=['site_id', 'timestamp'])\ntrain_meta_weather_no_outlier.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Because we are predicting hourly meter reading, we will add `hour` as an engineered feature as well:"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_meta_weather_no_outlier['hour'] = train_meta_weather_no_outlier.timestamp.dt.hour","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now we will compute meter-specific correlations:"},{"metadata":{"trusted":true},"cell_type":"code","source":"for m in meters:\n    print(meters[m])\n    display(train_meta_weather_no_outlier[train_meta_weather_no_outlier.meter==m][train_meta_weather_no_outlier.columns[-8:]].corr())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"It appears that none of the features show strong correlations with our target variable `meter_reading`. However, some features are highly correlated with one another, e.g. `air_temperature` and `dew_temperature`, or `wind_direction` and `wind_speed`. It might be worth noting for future feature selections."},{"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}