{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Neighborhood Structures in the ArcGIS Spatial Statistics Library\n", "1. Spatial Weights Matrix\n", "2. On-the-fly Neighborhood Iterators [GA Table]\n", "3. Contructing PySAL Spatial Weights " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Spatial Weight Matrix File\n", "1. Stores the spatial weights so they do not have to be re-calculated for each analysis.\n", "2. In row-compressed format. \n", "3. Little endian byte encoded.\n", "4. Requires a unique long/short field to identify each features. **Can NOT be the OID/FID.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Construction\n" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import Weights as WEIGHTS\n", "import os as OS\n", "inputFC = r'../data/CA_Polygons.shp'\n", "fullFC = OS.path.abspath(inputFC)\n", "fullPath, fcName = OS.path.split(fullFC)\n", "masterField = \"MYID\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Distance-Based Options\n", "``` INPUTS: \n", " inputFC (str): path to the input feature class\n", " swmFile (str): path to the SWM file.\n", " masterField (str): field in table that serves as the mapping.\n", " fixed (boolean): fixed (1) or inverse (0) distance? \n", " concept: {str, EUCLIDEAN}: EUCLIDEAN or MANHATTAN \n", " exponent {float, 1.0}: distance decay\n", " threshold {float, None}: distance threshold\n", " kNeighs (int): number of neighbors to return\n", " rowStandard {bool, True}: row standardize weights?\n", "```\n", "\n", "*Example: Fixed Distance*" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "collapsed": true }, "outputs": [], "source": [ "swmFile = OS.path.join(fullPath, \"fixed250k.swm\")\n", "fixedSWM = WEIGHTS.distance2SWM(fullFC, swmFile, masterField, \n", " threshold = 250000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Example: Inverse Distance Squared*" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "collapsed": true }, "outputs": [], "source": [ "swmFile = OS.path.join(fullPath, \"inv2_250k.swm\")\n", "fixedSWM = WEIGHTS.distance2SWM(fullFC, swmFile, masterField, fixed = False,\n", " exponent = 2.0, threshold = 250000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### k-Nearest Neighbors Options\n", "``` INPUTS: \n", " inputFC (str): path to the input feature class\n", " swmFile (str): path to the SWM file.\n", " masterField (str): field in table that serves as the mapping.\n", " concept: {str, EUCLIDEAN}: EUCLIDEAN or MANHATTAN \n", " kNeighs {int, 1}: number of neighbors to return\n", " rowStandard {bool, True}: row standardize weights?\n", "```\n", "\n", "*Example: 8-nearest neighbors*" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "collapsed": true }, "outputs": [], "source": [ "swmFile = OS.path.join(fullPath, \"knn8.swm\")\n", "fixedSWM = WEIGHTS.kNearest2SWM(fullFC, swmFile, masterField, kNeighs = 8)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Example: Fixed Distance - k-nearest neighbor hybrid [i.e. at least k neighbors but may have more...]*" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "collapsed": true }, "outputs": [], "source": [ "swmFile = OS.path.join(fullPath, \"fixed250k_knn8.swm\")\n", "fixedSWM = WEIGHTS.distance2SWM(fullFC, swmFile, masterField, kNeighs = 8,\n", " threshold = 250000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Delaunay Triangulation Options\n", "``` INPUTS: \n", " inputFC (str): path to the input feature class\n", " swmFile (str): path to the SWM file.\n", " masterField (str): field in table that serves as the mapping.\n", " rowStandard {bool, True}: row standardize weights?\n", "```\n", "\n", "*Example: delaunay*" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "collapsed": true }, "outputs": [], "source": [ "swmFile = OS.path.join(fullPath, \"delaunay.swm\")\n", "fixedSWM = WEIGHTS.delaunay2SWM(fullFC, swmFile, masterField)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Polygon Contiguity Options \n", "``` INPUTS: \n", " inputFC (str): path to the input feature class\n", " swmFile (str): path to the SWM file.\n", " masterField (str): field in table that serves as the mapping.\n", " concept: {str, EUCLIDEAN}: EUCLIDEAN or MANHATTAN\n", " kNeighs {int, 0}: number of neighbors to return (1)\n", " rowStandard {bool, True}: row standardize weights?\n", " contiguityType {str, Rook}: {Rook = Edges Only, Queen = Edges/Vertices}\n", "\n", " NOTES:\n", " (1) kNeighs is an option often used when you know there are polygon\n", " features that are not contiguous (e.g. islands). A kNeighs value \n", " of 2 will assure that ALL features have at least 2 neighbors. \n", " If a polygon is determined to only touch a single other polygon, \n", " then a nearest neighbor search based on true centroids are used \n", " to find the additional neighbor.\n", "```\n", "\n", "*Example: Rook [Binary]*" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "collapsed": true }, "outputs": [], "source": [ "swmFile = OS.path.join(fullPath, \"rook_bin.swm\")\n", "WEIGHTS.polygon2SWM(inputFC, swmFile, masterField, rowStandard = False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Example: Queen Contiguity [Row Standardized]" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "collapsed": true }, "outputs": [], "source": [ "swmFile = OS.path.join(fullPath, \"queen.swm\")\n", "WEIGHTS.polygon2SWM(inputFC, swmFile, masterField, contiguityType = \"QUEEN\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Example: Queen Contiguity - KNN Hybrid [Prevents Islands w/ no Neighbors][\n", "(1)](#poly_options)*" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "collapsed": true }, "outputs": [], "source": [ "swmFile = OS.path.join(fullPath, \"hybrid.swm\")\n", "WEIGHTS.polygon2SWM(inputFC, swmFile, masterField, kNeighs = 4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# On-the-fly Neighborhood Iterators [GA Table]\n", "1. Reads centroids of input features into spatial tree structure.\n", "2. Distance Based Queries. \n", "3. Scalable: In-memory/disk-space swap for large data.\n", "4. Requires a unique long/short field to identify each features. **Can be the OID/FID.**\n", "5. Uses ```requireSearch = True``` when using ```ssdo.obtainData```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Pre-Example: Load the Data into GA Version of SSDataObject*" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import SSDataObject as SSDO\n", "inputFC = r'../data/CA_Polygons.shp'\n", "ssdo = SSDO.SSDataObject(inputFC)\n", "uniqueIDField = ssdo.oidName\n", "ssdo.obtainData(uniqueIDField, requireSearch = True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Example: NeighborSearch - When you only need your Neighbor IDs*\n", "\n", "*gaSearch.init_nearest(distance_band, minimum_num_neighs, {\"euclidean\", \"manhattan\")*" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[23 27 1 2]\n", "[ 2 0 27 23]\n", "[ 1 27 0 38]\n", "[4 1 5 6]\n", "[3 5 2 1]\n" ] } ], "source": [ "import arcgisscripting as ARC\n", "import WeightsUtilities as WU\n", "import gapy as GAPY\n", "gaSearch = GAPY.ga_nsearch(ssdo.gaTable)\n", "concept, gaConcept = WU.validateDistanceMethod('EUCLIDEAN', ssdo.spatialRef)\n", "gaSearch.init_nearest(0.0, 4, gaConcept)\n", "neighSearch = ARC._ss.NeighborSearch(ssdo.gaTable, gaSearch)\n", "for i in range(len(neighSearch)):\n", " neighOrderIDs = neighSearch[i]\n", " if i < 5:\n", " print(neighOrderIDs)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "METER\n", "ID 0 has 3 neighs, they are 3, 1, 4\n", "The Distances are... 200.00, 200.00, 282.84\n", "ID 1 has 5 neighs, they are 2, 0, 4, 3, 5\n", "The Distances are... 200.00, 200.00, 200.00, 282.84, 282.84\n", "ID 2 has 4 neighs, they are 5, 1, 11, 4\n", "The Distances are... 200.00, 200.00, 282.84, 282.84\n", "ID 3 has 5 neighs, they are 0, 4, 6, 1, 7\n", "The Distances are... 200.00, 200.00, 200.00, 282.84, 282.84\n", "ID 4 has 8 neighs, they are 5, 1, 3, 7, 6, 2, 0, 8\n", "The Distances are... 200.00, 200.00, 200.00, 200.00, 282.84, 282.84, 282.84, 282.84\n" ] } ], "source": [ "import arcgisscripting as ARC\n", "import WeightsUtilities as WU\n", "import gapy as GAPY\n", "import SSUtilities as UTILS\n", "inputGrid = r'D:\\Data\\UC\\UC17\\Island\\Dykstra\\Dykstra.gdb\\emerge'\n", "ssdo = SSDO.SSDataObject(inputGrid)\n", "ssdo.obtainData(ssdo.oidName, requireSearch = True)\n", "gaSearch = GAPY.ga_nsearch(ssdo.gaTable)\n", "concept, gaConcept = WU.validateDistanceMethod('EUCLIDEAN', ssdo.spatialRef)\n", "gaSearch.init_nearest(300., 0, gaConcept)\n", "neighSearch = ARC._ss.NeighborSearch(ssdo.gaTable, gaSearch)\n", "print(ssdo.distanceInfo.name)\n", "for i in range(len(neighSearch)):\n", " neighOrderIDs = neighSearch[i]\n", " x0,y0 = ssdo.xyCoords[i]\n", " if i < 5:\n", " nhs = \", \".join([str(i) for i in neighOrderIDs])\n", " dist = []\n", " for nh in neighOrderIDs:\n", " x1,y1 = ssdo.xyCoords[nh]\n", " dij = WU.euclideanDistance(x0,x1,y0,y1)\n", " dist.append(UTILS.formatValue(dij, \"%0.2f\"))\n", " print(\"ID {0} has {1} neighs, they are {2}\".format(i, len(neighOrderIDs), nhs))\n", " print(\"The Distances are... {0}\".format(\", \".join(dist)))\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Example: NeighborWeights - When you need non-uniform spatial weights (E.g. Inverse Distance Squared)*\n", "\n", "*NeighborWeights(gaTable, gaSearch, weight_type [0: inverse_distance, **1: fixed_distance**], exponent = 1.0, row_standard = True, include_self = False)*" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[23 27 1 2 26 22 25 29 38 24 21 4 5 34 28 3 42 41 35 31 33]\n", "[ 0.24396241 0.15676701 0.13038913 0.09121172 0.07828545 0.05179581\n", " 0.03150761 0.02784675 0.02374503 0.02282472 0.01887078 0.01625245\n", " 0.01579714 0.01565997 0.01562422 0.01241947 0.01037579 0.00957457\n", " 0.00936652 0.00911591 0.00860753]\n", "[ 2 0 27 23 4 26 38 3 5 29 22 25 24 28 42 34 21 46 41]\n", "[ 0.36443032 0.15271508 0.09036452 0.0526977 0.04303312 0.04294889\n", " 0.03623002 0.03043097 0.03002032 0.02873118 0.02299465 0.02071245\n", " 0.01442619 0.01389529 0.01204944 0.01178428 0.01173792 0.01047316\n", " 0.01032449]\n", "[ 1 27 0 38 26 5 29 23 4 3 25 22 28 42 24 46 41 34 9 45 21 10]\n", "[ 0.30264594 0.13468572 0.08871796 0.06402787 0.05237183 0.0432296\n", " 0.04296864 0.04237608 0.04219547 0.02342009 0.02259267 0.01885412\n", " 0.01616961 0.01484024 0.01375183 0.01230613 0.01214131 0.01207259\n", " 0.01106552 0.01034444 0.01003139 0.00919095]\n" ] } ], "source": [ "gaSearch = GAPY.ga_nsearch(ssdo.gaTable)\n", "gaSearch.init_nearest(250000, 0, gaConcept)\n", "neighSearch = ARC._ss.NeighborWeights(ssdo.gaTable, gaSearch, weight_type = 0, exponent = 2.0)\n", "for i in range(len(neighSearch)):\n", " neighOrderIDs, neighWeights = neighSearch[i]\n", " if i < 3:\n", " print(neighOrderIDs)\n", " print(neighWeights)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Contructing PySAL Spatial Weights \n", "1. Convert masterID to orderID when using ssdo.obtainData (SWM File, Polygon Contiguity) \n", "2. Data is already in orderID when using ssdo.obtainDataGA (Distance Based)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Methods in next cell can be imported from pysal2ArcGIS.py**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import pysal as PYSAL\n", "import WeightsUtilities as WU\n", "import SSUtilities as UTILS\n", "\n", "def swm2Weights(ssdo, swmfile):\n", " \"\"\"Converts ArcGIS Sparse Spatial Weights Matrix (*.swm) file to \n", " PySAL Sparse Spatial Weights Class.\n", " \n", " INPUTS:\n", " ssdo (class): instance of SSDataObject [1,2]\n", " swmFile (str): full path to swm file\n", " \n", " NOTES:\n", " (1) Data must already be obtained using ssdo.obtainData()\n", " (2) The masterField for the swm file and the ssdo object must be\n", " the same and may NOT be the OID/FID/ObjectID\n", " \"\"\"\n", " neighbors = {}\n", " weights = {}\n", " \n", " #### Create SWM Reader Object ####\n", " swm = WU.SWMReader(swmfile)\n", " \n", " #### SWM May NOT be a Subset of the Data ####\n", " if ssdo.numObs > swm.numObs:\n", " ARCPY.AddIDMessage(\"ERROR\", 842, ssdo.numObs, swm.numObs)\n", " raise SystemExit()\n", " \n", " #### Parse All SWM Records ####\n", " for r in UTILS.ssRange(swm.numObs):\n", " info = swm.swm.readEntry()\n", " masterID, nn, nhs, w, sumUnstandard = info\n", " \n", " #### Must Have at Least One Neighbor ####\n", " if nn:\n", " #### Must be in Selection Set (If Exists) ####\n", " if masterID in ssdo.master2Order:\n", " outNHS = []\n", " outW = []\n", " \n", " #### Transform Master ID to Order ID ####\n", " orderID = ssdo.master2Order[masterID]\n", " \n", " #### Neighbors and Weights Adjusted for Selection ####\n", " for nhInd, nhVal in enumerate(nhs):\n", " try:\n", " nhOrder = ssdo.master2Order[nhVal]\n", " outNHS.append(nhOrder)\n", " weightVal = w[nhInd]\n", " if swm.rowStandard:\n", " weightVal = weightVal * sumUnstandard[0]\n", " outW.append(weightVal)\n", " except KeyError:\n", " pass\n", " \n", " #### Add Selected Neighbors/Weights ####\n", " if len(outNHS):\n", " neighbors[orderID] = outNHS\n", " weights[orderID] = outW\n", " swm.close()\n", " \n", " #### Construct PySAL Spatial Weights and Standardize as per SWM ####\n", " w = PYSAL.W(neighbors, weights)\n", " if swm.rowStandard:\n", " w.transform = 'R'\n", " \n", " return w\n", "\n", "def poly2Weights(ssdo, contiguityType = \"ROOK\", rowStandard = True):\n", " \"\"\"Uses GP Polygon Neighbor Tool to construct contiguity relationships\n", " and stores them in PySAL Sparse Spatial Weights class.\n", " \n", " INPUTS:\n", " ssdo (class): instance of SSDataObject [1]\n", " contiguityType {str, ROOK}: ROOK or QUEEN contiguity\n", " rowStandard {bool, True}: whether to row standardize the spatial weights\n", " \n", " NOTES:\n", " (1) Data must already be obtained using ssdo.obtainData() or ssdo.obtainDataGA ()\n", " \"\"\"\n", " \n", " neighbors = {}\n", " weights = {}\n", " polyNeighDict = WU.polygonNeighborDict(ssdo.inputFC, ssdo.masterField,\n", " contiguityType = contiguityType)\n", " \n", " for masterID, neighIDs in UTILS.iteritems(polyNeighDict):\n", " orderID = ssdo.master2Order[masterID]\n", " neighbors[orderID] = [ssdo.master2Order[i] for i in neighIDs]\n", "\n", " w = PYSAL.W(neighbors)\n", " if rowStandard:\n", " w.transform = 'R'\n", " return w\n", "\n", "def distance2Weights(ssdo, neighborType = 1, distanceBand = 0.0, numNeighs = 0, distanceType = \"euclidean\",\n", " exponent = 1.0, rowStandard = True, includeSelf = False):\n", " \"\"\"Uses ArcGIS Neighborhood Searching Structure to create a PySAL Sparse Spatial Weights Matrix.\n", " \n", " INPUTS:\n", " ssdo (class): instance of SSDataObject [1]\n", " neighborType {int, 1}: 0 = inverse distance, 1 = fixed distance, \n", " 2 = k-nearest-neighbors, 3 = delaunay\n", " distanceBand {float, 0.0}: return all neighbors within this distance for inverse/fixed distance\n", " numNeighs {int, 0}: number of neighbors for k-nearest-neighbor, can also be used to set a minimum \n", " number of neighbors for inverse/fixed distance\n", " distanceType {str, euclidean}: manhattan or euclidean distance [2] \n", " exponent {float, 1.0}: distance decay factor for inverse distance\n", " rowStandard {bool, True}: whether to row standardize the spatial weights\n", " includeSelf {bool, False}: whether to return self as a neighbor\n", " \n", " NOTES:\n", " (1) Data must already be obtained using ssdo.obtainDataGA()\n", " (2) Chordal Distance is used for GCS Data\n", " \"\"\"\n", " \n", " neighbors = {}\n", " weights = {}\n", " gaSearch = GAPY.ga_nsearch(ssdo.gaTable)\n", " if neighborType == 3:\n", " gaSearch.init_delaunay()\n", " neighSearch = ARC._ss.NeighborWeights(ssdo.gaTable, gaSearch, weight_type = 1)\n", " else:\n", " if neighborType == 2:\n", " distanceBand = 0.0\n", " weightType = 1\n", " else:\n", " weightType = neighborType\n", " \n", " concept, gaConcept = WU.validateDistanceMethod(distanceType.upper(), ssdo.spatialRef)\n", " gaSearch.init_nearest(distanceBand, numNeighs, gaConcept)\n", " neighSearch = ARC._ss.NeighborWeights(ssdo.gaTable, gaSearch, weight_type = weightType, \n", " exponent = exponent, include_self = includeSelf)\n", " \n", " for i in range(len(neighSearch)):\n", " neighOrderIDs, neighWeights = neighSearch[i]\n", " neighbors[i] = neighOrderIDs\n", " weights[i] = neighWeights\n", " \n", " w = PYSAL.W(neighbors, weights)\n", " if rowStandard:\n", " w.transform = 'R'\n", " return w " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Converting Spatial Weight Matrix Formats (e.g. *.swm, *.gwt, *.gal)\n", "\n", "- Follow directions at the PySAL-ArcGIS-Toolbox Git Repository [https://github.com/Esri/PySAL-ArcGIS-Toolbox]\n", "- Please make note of the section on **Adding a Git Project to your ArcGIS Installation Python Path**.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import WeightConvertor as W_CONVERT\n", "swmFile = OS.path.join(fullPath, \"queen.swm\")\n", "galFile = OS.path.join(fullPath, \"queen.gal\")\n", "convert = W_CONVERT.WeightConvertor(swmFile, galFile, inputFC, \"MYID\", \"SWM\", \"GAL\")\n", "convert.createOutput()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "**Calling MaxP Regions Using SWM Based on Rook Contiguity, No Row Standardization**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as NUM\n", "NUM.random.seed(100)\n", "ssdo = SSDO.SSDataObject(inputFC)\n", "uniqueIDField = \"MYID\"\n", "fieldNames = ['PCR2010', 'POP2010', 'PERCNOHS']\n", "ssdo.obtainDataGA(uniqueIDField, fieldNames)\n", "df = ssdo.getDataFrame()\n", "X = df.as_matrix()\n", "swmFile = OS.path.join(fullPath, \"rook_bin.swm\")\n", "w = swm2Weights(ssdo, swmFile)\n", "maxp = PYSAL.region.Maxp(w, X[:,0:2], 3000000., floor_variable = X[:,2])\n", "maxpGroups = NUM.empty((ssdo.numObs,), int)\n", "for regionID, orderIDs in enumerate(maxp.regions):\n", " maxpGroups[orderIDs] = regionID\n", " print((regionID, orderIDs))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Calling MaxP Regions Using Rook Contiguity, No Row Standardization**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "NUM.random.seed(100)\n", "w = poly2Weights(ssdo, rowStandard = False)\n", "maxp = PYSAL.region.Maxp(w, X[:,0:2], 3000000., floor_variable = X[:,2])\n", "maxpGroups = NUM.empty((ssdo.numObs,), int)\n", "for regionID, orderIDs in enumerate(maxp.regions):\n", " maxpGroups[orderIDs] = regionID\n", " print((regionID, orderIDs))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Identical results because the random seed was set to 100 and they have the same spatial neighborhood**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Calling MaxP Regions Using Fixed Distance 250000, Hyrbid to Assure at least 2 Neighbors**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "NUM.random.seed(100)\n", "w = distance2Weights(ssdo, distanceBand = 250000.0, numNeighs = 2)\n", "maxp = PYSAL.region.Maxp(w, X[:,0:2], 3000000., floor_variable = X[:,2])\n", "maxpGroups = NUM.empty((ssdo.numObs,), int)\n", "for regionID, orderIDs in enumerate(maxp.regions):\n", " maxpGroups[orderIDs] = regionID\n", " print((regionID, orderIDs))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "**Same random seed, different result as neighborhood is different than the two previous**" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.3" }, "widgets": { "state": {}, "version": "1.1.2" } }, "nbformat": 4, "nbformat_minor": 1 }