[pysal] 05/17: Imported Upstream version 1.11.1

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Sat Jun 11 20:44:26 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository pysal.

commit b37e6485ad764cde9b9a2275327ef18edd8c1636
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sat Jun 11 17:12:00 2016 +0200

    Imported Upstream version 1.11.1
---
 .github/CONTRIBUTING.md                 |    2 +
 .github/ISSUE_TEMPLATE.md               |   30 +
 .github/PULL_REQUEST_TEMPLATE.md        |   12 +
 .gitignore                              |    2 +-
 .travis.yml                             |    2 +-
 CHANGELOG.txt                           |   76 +-
 README.rst                              |    2 +
 doc/source/conf.py                      |    4 +-
 doc/source/developers/guidelines.rst    |   38 +-
 doc/source/developers/release.rst       |    3 +-
 doc/source/index.rst                    |    2 +-
 mk2                                     |    2 -
 mk3                                     |    4 -
 pysal/cg/shapes.py                      |   14 +-
 pysal/cg/tests/test_geoJSON.py          |   28 +
 pysal/contrib/viz/folium_mapping.ipynb  | 2116 +++++++++----------------------
 pysal/contrib/viz/folium_mapping.py     |   14 +-
 pysal/core/util/wkb.py                  |  230 ++++
 pysal/core/util/wkt.py                  |    8 -
 pysal/esda/gamma.py                     |   20 +-
 pysal/esda/mapclassify.py               |  327 ++++-
 pysal/esda/moran.py                     |   16 +-
 pysal/esda/tests/test_mapclassify.py    |   47 +-
 pysal/spatial_dynamics/interaction.py   |    4 +-
 pysal/spreg/tests/test_ml_error.py      |    2 +-
 pysal/spreg/tests/test_sur_error.py     |   23 +-
 pysal/spreg/user_output.py              |    6 +-
 pysal/version.py                        |    4 +-
 pysal/weights/Contiguity.py             |    8 +-
 pysal/weights/Distance.py               |   35 +-
 pysal/weights/spatial_lag.py            |  130 +-
 pysal/weights/tests/test_spatial_lag.py |   28 +-
 pysal/weights/tests/test_user.py        |    7 +-
 pysal/weights/util.py                   |   62 +-
 pysal/weights/weights.py                |   12 +-
 setup.py                                |    2 +-
 touch.txt                               |    1 -
 37 files changed, 1669 insertions(+), 1654 deletions(-)

diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..9c1c3a9
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,2 @@
+Thank you for your interest in contributing! We work primarily on Github. Please
+review the [contributing procedures](https://github.com/pysal/pysal/wiki/GitHub-Standard-Operating-Procedures) so that we can accept your contributions! Alternatively, contact someone in the [development chat channel](https://gitter.im/pysal.pysal).
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..5391dac
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,30 @@
+Thank you for filing this issue! To help troubleshoot this issue, please follow
+the following directions to the best of your ability before submitting an issue.
+Feel free to delete this text once you've filled out the relevant requests. 
+
+Please include the output of the following in your issue submission. If you don't know how to provide the information, commands to get the relevant information from the Python interpreter will follow each bullet point.
+
+Feel free to delete the commands after you've filled out each bullet. 
+
+- Platform information:
+```python
+>>> import os; print(os.name, os.sys.platform);print(os.uname())
+```
+- Python version: 
+```python
+>>> import sys; print(sys.version)
+```
+- SciPy version:
+```python
+>>> import scpiy; print(scipy.__version__)
+```
+- NumPy version:
+```python
+>>> import numpy; print(numpy.__version__)
+```
+
+Also, please upload any relevant data as [a file
+attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/). Please **do not** upload pickled objects, since it's nearly impossible to troubleshoot them without replicating your exact namespace. Instead, provide the minimal subset of the data required to replicate the problem. If it makes you more comfortable submitting the issue, feel free to:
+
+1. remove personally identifying information from data or code
+2. provide only the required subset of the full data or code 
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..d5173f9
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,12 @@
+Hello! Please make sure to check all these boxes before submitting a Pull Request
+(PR). Once you have checked the boxes, feel free to remove all text except the
+justification in point 5. 
+
+1. [ ] You have run tests on this submission, either by using [Travis Continuous Integration testing](https://github.com/pysal/pysal/wiki/GitHub-Standard-Operating-Procedures#automated-testing-w-travis-ci) testing or running `nosetests` on your changes?
+2. [ ] This pull request is directed to the `pysal/dev` branch.
+3. [ ] This pull introduces new functionality covered by
+   [docstrings](https://en.wikipedia.org/wiki/Docstring#Python) and
+   [unittests](https://docs.python.org/2/library/unittest.html)? 
+4. [ ] You have [assigned a
+   reviewer](https://help.github.com/articles/assigning-issues-and-pull-requests-to-other-github-users/) and added relevant [labels](https://help.github.com/articles/applying-labels-to-issues-and-pull-requests/)
+5. [ ] The justification for this PR is: 
diff --git a/.gitignore b/.gitignore
index 6c82954..6607001 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,7 +77,7 @@ pysal/examples/south.prj
 
 #Vi
 *.swp
-
+.ropeproject/
 .eggs/
 pysal/contrib/planar/
 pysal/esda/.ropeproject/
diff --git a/.travis.yml b/.travis.yml
index 82bf789..c584b4a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,7 +20,7 @@ before_install:
   - if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then rm -rf pysal/contrib; 2to3 -nw pysal/ > /dev/null; fi
 
 install:
-  - conda install --yes numpy=1.10.2 scipy=0.16.0 nose pip
+  - conda install --yes numpy scipy nose pip
   - pip install -r travis.txt
 
 script: 
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 1506e43..8151921 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,6 +1,80 @@
+v<1.11.1>, 2016-04-01
+
+We closed a total of 62 issues, 20 pull requests and 42 regular issues;
+this is the full list (generated with the script 
+:file:`tools/github_stats.py`):
+
+Pull Requests (20):
+
+* :ghpull:`777`: fix minor issues with using stdlib warnings in mapclassify.py
+* :ghpull:`766`: Constant check
+* :ghpull:`781`: Dev
+* :ghpull:`778`: Wkb
+* :ghpull:`776`: Updating & find-bin-as-call for Map_Classifiers
+* :ghpull:`770`: adding github chrome to make project contributions easier to handle
+* :ghpull:`764`: add folium changes needed for notebook to run
+* :ghpull:`760`: Update docs for #697
+* :ghpull:`763`: docs fix: incorrect array dimensions listed for spatial interaction SpaceTimeEvents
+* :ghpull:`756`: B726
+* :ghpull:`749`: remove cruft in git root and add gitter badge to the readme
+* :ghpull:`748`: Replace deprecated np.rank with np.ndim
+* :ghpull:`745`: Lag Categorical & Find Bins
+* :ghpull:`741`: fix for #740
+* :ghpull:`739`: Dev
+* :ghpull:`738`: fixing master version
+* :ghpull:`737`: Bumping dev
+* :ghpull:`736`: Merge pull request #734 from sjsrey/master
+* :ghpull:`735`: Dev in sync with master for 1.11
+* :ghpull:`734`: Release 1.11
+
+Issues (42):
+
+* :ghissue:`773`: add isKDTree typecomparison to handle divergent cKDTree and KDTree types
+* :ghissue:`777`: fix minor issues with using stdlib warnings in mapclassify.py
+* :ghissue:`766`: Constant check
+* :ghissue:`782`: Contrib docs
+* :ghissue:`781`: Dev
+* :ghissue:`762`: viz: folium_mapping.ipynb AttributeError: 'Map' object has no attribute '_build_map'
+* :ghissue:`778`: Wkb
+* :ghissue:`776`: Updating & find-bin-as-call for Map_Classifiers
+* :ghissue:`770`: adding github chrome to make project contributions easier to handle
+* :ghissue:`774`: Added a prototype for constructing weights from a list of shapely Polygons
+* :ghissue:`772`: knnW user guide doc error
+* :ghissue:`765`: potential constant_check bug
+* :ghissue:`759`: Fixed code in ipython notebooks
+* :ghissue:`752`: [WIP] Add J function to network submodule
+* :ghissue:`764`: add folium changes needed for notebook to run
+* :ghissue:`760`: Update docs for #697
+* :ghissue:`763`: docs fix: incorrect array dimensions listed for spatial interaction SpaceTimeEvents
+* :ghissue:`750`: Add gitter badge to README on master branch
+* :ghissue:`758`: Fixed code in ipython notebooks
+* :ghissue:`755`: add speedup of conditional randomization
+* :ghissue:`726`: Compatibility for Scipy 16.1
+* :ghissue:`756`: B726
+* :ghissue:`749`: remove cruft in git root and add gitter badge to the readme
+* :ghissue:`587`: ML Lag
+* :ghissue:`748`: Replace deprecated np.rank with np.ndim
+* :ghissue:`747`: Replace deprecated np.rank with np.ndim
+* :ghissue:`653`: network is returning NAN's on diagonal of distance matrix
+* :ghissue:`660`: insert zeros on symmetric matrix diagonal
+* :ghissue:`745`: Lag Categorical & Find Bins
+* :ghissue:`744`: [REBASED] Update moran.py with much faster iterations
+* :ghissue:`732`: Update moran.py with much faster iterations
+* :ghissue:`743`: [REBASED]: Update moran.py with much faster iterations
+* :ghissue:`742`: Links not working
+* :ghissue:`740`: Moran_Local's EI is returned as an array instead of a float
+* :ghissue:`741`: fix for #740
+* :ghissue:`739`: Dev
+* :ghissue:`738`: fixing master version
+* :ghissue:`737`: Bumping dev
+* :ghissue:`151`: Port pysal to python3
+* :ghissue:`736`: Merge pull request #734 from sjsrey/master
+* :ghissue:`735`: Dev in sync with master for 1.11
+* :ghissue:`734`: Release 1.11
+
 v<1.11.0>, 2016-01-27
 
-GitHub stats for 2015/01/31 - 2015/07/29
+GitHub stats for 2015/07/29 - 2016/01/27
 
 These lists are automatically generated, and may be incomplete or contain duplicates.
 
diff --git a/README.rst b/README.rst
index 426b593..4f342de 100644
--- a/README.rst
+++ b/README.rst
@@ -7,6 +7,8 @@ Python Spatial Analysis Library
 .. image:: https://coveralls.io/repos/pysal/pysal/badge.svg?branch=master
    :target: https://coveralls.io/r/pysal/pysal?branch=master
 
+.. image:: https://badges.gitter.im/pysal/pysal.svg
+   :target: https://gitter.im/pysal/pysal
 
 PySAL_ is an open source cross-platform library of spatial analysis functions
 written in Python. It is intended to support the development of high level
diff --git a/doc/source/conf.py b/doc/source/conf.py
index c05a19c..35aa1e7 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -57,9 +57,9 @@ copyright = u'2014-, PySAL Developers; 2009-13 Sergio Rey'
 # built documents.
 #
 # The short X.Y version.
-version = '1.11.0'
+version = '1.11.1'
 # The full version, including alpha/beta/rc tags.
-release = '1.11.0'
+release = '1.11.1'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/doc/source/developers/guidelines.rst b/doc/source/developers/guidelines.rst
index d5a8137..3e2c651 100644
--- a/doc/source/developers/guidelines.rst
+++ b/doc/source/developers/guidelines.rst
@@ -38,6 +38,11 @@ Source Code
 PySAL uses `git <http://git-scm.com/>`_ and github for our  `code repository <https://github.com/pysal/pysal.git/>`_.
 
 
+Please see `our procedures and policies for development on GitHub <https://github.com/pysal/pysal/wiki/GitHub-Standard-Operating-Procedures>`_
+as well as how to `configure your local git for development
+<https://github.com/pysal/pysal/wiki/Example-git-config>`_.
+
+
 You can setup PySAL for local development following the :doc:`installation instructions </users/installation>`.
 
 
@@ -46,7 +51,8 @@ Development Mailing List
 ------------------------
 
 Development discussions take place on `pysal-dev
-<http://groups.google.com/group/pysal-dev>`_.
+<http://groups.google.com/group/pysal-dev>`_
+and the `gitter room <https://gitter.im/pysal/pysal>`_.
 
 
 -----------------------
@@ -57,21 +63,6 @@ PySAL development follows a six-month release schedule that is aligned with
 the academic calendar.
 
 
-1.11 Cycle
-==========
-
-========   ========   ================= ====================================================
-Start      End        Phase             Notes
-========   ========   ================= ====================================================
-8/1/15      8/14/15   Module Proposals  Developers draft PEPs and prototype
-8/15/15     8/15/15   Developer vote    All developers vote on PEPs 
-8/16/15     8/16/15   Module Approval   BDFL announces final approval
-8/17/15    12/30/15   Development       Implementation and testing of approved modules
-1/1/16       1/1/16   Code Freeze       APIs fixed, bug and testing changes only
-1/23/16     1/30/16   Release Prep      Test release builds, updating svn 
-1/31/16     1/31/16   Release           Official release of 1.11
-========   ========   ================= ====================================================
-
 1.12 Cycle
 ==========
 
@@ -88,6 +79,21 @@ Start      End        Phase             Notes
 ========   ========   ================= ====================================================
 
 
+1.13 Cycle
+==========
+
+========   ========   ================= ====================================================
+Start      End        Phase             Notes
+========   ========   ================= ====================================================
+8/1/16      8/14/16   Module Proposals  Developers draft PEPs and prototype
+8/16/16     8/16/16   Developer vote    All developers vote on PEPs 
+8/16/16     8/16/16   Module Approval   BDFL announces final approval
+8/17/16    12/30/16   Development       Implementation and testing of approved modules
+1/1/17       1/1/17   Code Freeze       APIs fixed, bug and testing changes only
+1/23/17     1/30/17   Release Prep      Test release builds, updating svn 
+1/31/17     1/31/17   Release           Official release of 1.13
+========   ========   ================= ====================================================
+
 
 ----------
 Governance
diff --git a/doc/source/developers/release.rst b/doc/source/developers/release.rst
index 953bf93..6322e45 100644
--- a/doc/source/developers/release.rst
+++ b/doc/source/developers/release.rst
@@ -13,8 +13,9 @@ Prepare the release
 - Check all tests pass. See :doc:`testing`.
 - Update CHANGELOG::
 
-     $ python tools/github_stats.py >> chglog
+     $ python tools/github_stats.py days >> chglog
 
+- where `days` is the number of days to start the logs at
 - Prepend `chglog` to `CHANGELOG` and edit
 - Edit THANKS and README and README.md if needed.
 - Edit the file `version.py` to update MAJOR, MINOR, MICRO
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 4407ec8..b07700a 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -20,7 +20,7 @@ PySAL
 
 .. sidebar:: Releases
 
-    - `Stable 1.11.0 (Released 2016-1-27) <users/installation.html>`_
+    - `Stable 1.11.1 (Released 2016-4-1) <users/installation.html>`_
     - `Development  <http://github.com/pysal/pysal/tree/dev>`_
 
 PySAL is an open source library of spatial analysis functions written in
diff --git a/mk2 b/mk2
deleted file mode 100755
index c9e4a8b..0000000
--- a/mk2
+++ /dev/null
@@ -1,2 +0,0 @@
-git checkout py3conv_cleaning
-git stash
diff --git a/mk3 b/mk3
deleted file mode 100755
index 200e318..0000000
--- a/mk3
+++ /dev/null
@@ -1,4 +0,0 @@
-git branch -d 2to3
-git checkout -b 2to3
-python-modernize -nw -j 4 --future-unicode ./pysal
-2to3 -nw -j 4 ./pysal
diff --git a/pysal/cg/shapes.py b/pysal/cg/shapes.py
index 0e1f891..6f10097 100644
--- a/pysal/cg/shapes.py
+++ b/pysal/cg/shapes.py
@@ -1016,12 +1016,20 @@ class Chain(object):
 
     @classmethod
     def __from_geo_interface__(cls, geo):
-        verts = [Point(pt) for pt in geo['coordinates']]
+        if geo['type'].lower() == 'linestring':
+            verts = [Point(pt) for pt in geo['coordinates']]
+        elif geo['type'].lower() == 'multilinestring':
+            verts = [map(Point, part) for part in geo['coordinates']]
+        else:
+            raise TypeError('%r is not a Chain'%geo)
         return cls(verts)
 
     @property
     def __geo_interface__(self):
-        return {'type': 'LineString', 'coordinates': self.vertices}
+        if len(self.parts) == 1:
+            return {'type': 'LineString', 'coordinates': self.vertices}
+        else:
+            return {'type': 'MultiLineString', 'coordinates': self.parts}
 
     def _reset_props(self):
         """
@@ -1945,7 +1953,7 @@ class Rectangle:
         return self.upper - self.lower
 
 
-_geoJSON_type_to_Pysal_type = {'point': Point, 'linestring': Chain,
+_geoJSON_type_to_Pysal_type = {'point': Point, 'linestring': Chain, 'multilinestring': Chain,
                                'polygon': Polygon, 'multipolygon': Polygon}
 import standalone  # moving this to top breaks unit tests !
 
diff --git a/pysal/cg/tests/test_geoJSON.py b/pysal/cg/tests/test_geoJSON.py
index 1940655..58635fc 100644
--- a/pysal/cg/tests/test_geoJSON.py
+++ b/pysal/cg/tests/test_geoJSON.py
@@ -1,4 +1,5 @@
 import pysal
+from pysal.cg.shapes import Point, Chain
 import doctest
 import unittest
 
@@ -19,6 +20,33 @@ class test_MultiPloygon(unittest.TestCase):
             self.assertEquals(json['type'],'MultiPolygon')
             self.assertEquals(str(shape.holes), str(poly.holes))
             self.assertEquals(str(shape.parts), str(poly.parts))
+class test_MultiLineString(unittest.TestCase):
+    def test_multipart_chain(self):
+        vertices = [[Point((0, 0)), Point((1, 0)), Point((1, 5))],
+                    [Point((-5, -5)), Point((-5, 0)), Point((0, 0))]]
+
+        #part A
+        chain0 = Chain(vertices[0])
+        #part B
+        chain1 = Chain(vertices[1])
+        #part A and B
+        chain2 = Chain(vertices)
+
+        json = chain0.__geo_interface__
+        self.assertEquals(json['type'], 'LineString')
+        self.assertEquals(len(json['coordinates']), 3)
+
+        json = chain1.__geo_interface__
+        self.assertEquals(json['type'], 'LineString')
+        self.assertEquals(len(json['coordinates']), 3)
+
+        json = chain2.__geo_interface__
+        self.assertEquals(json['type'], 'MultiLineString')
+        self.assertEquals(len(json['coordinates']), 2)
+
+        chain3 = pysal.cg.asShape(json)
+        self.assertEquals(chain2.parts, chain3.parts)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/pysal/contrib/viz/folium_mapping.ipynb b/pysal/contrib/viz/folium_mapping.ipynb
index 2f6828a..8aad55a 100644
--- a/pysal/contrib/viz/folium_mapping.ipynb
+++ b/pysal/contrib/viz/folium_mapping.ipynb
@@ -1,1550 +1,640 @@
 {
- "metadata": {
-  "name": "",
-  "signature": "sha256:a21899f81930013ea201161ec6b01b069282c7f5b36dd0ac860b43ccaf80eb49"
- },
- "nbformat": 3,
- "nbformat_minor": 0,
- "worksheets": [
+ "cells": [
   {
-   "cells": [
-    {
-     "cell_type": "heading",
-     "level": 1,
-     "metadata": {},
-     "source": [
-      "Mapping using Pysal and Folium: Example"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "import pysal as ps\n",
-      "import geojson as gj\n",
-      "import folium_mapping as fm"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [],
-     "prompt_number": 1
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "First, we need to convert the data into a JSON format. JSON, short for \"Javascript Serialized Object Notation,\" is a simple and effective way to represent objects in a digital environment. For geographic information, the [GeoJSON](https://geojson.org) standard defines how to represent geographic information in JSON format. Python programmers may be more comfortable thinking of JSON data as something akin to a standard Python dictionary. "
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "filepath = ps.examples.get_path('south.shp')[:-4]"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [],
-     "prompt_number": 2
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "shp = ps.open(filepath + '.shp')\n",
-      "dbf = ps.open(filepath + '.dbf')"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [],
-     "prompt_number": 3
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "js = fm.build_features(shp, dbf)"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [],
-     "prompt_number": 4
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Just to show, this constructs a dictionary with the following keys:"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "js.keys()"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 5,
-       "text": [
-        "['type', 'bbox', 'features']"
-       ]
-      }
-     ],
-     "prompt_number": 5
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "js.type"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 6,
-       "text": [
-        "'FeatureCollection'"
-       ]
-      }
-     ],
-     "prompt_number": 6
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "js.bbox"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 7,
-       "text": [
-        "[-106.6495132446289, 24.95596694946289, -75.0459976196289, 40.63713836669922]"
-       ]
-      }
-     ],
-     "prompt_number": 7
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "js.features[0]"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 8,
-       "text": [
-        "{\"bbox\": [-80.6688232421875, 40.39815902709961, -80.52220916748047, 40.63713836669922], \"geometry\": {\"coordinates\": [[[-80.6280517578125, 40.39815902709961], [-80.60203552246094, 40.480472564697266], [-80.62545776367188, 40.504398345947266], [-80.6336441040039, 40.53913879394531], [-80.6688232421875, 40.568214416503906], [-80.66793060302734, 40.58207321166992], [-80.63754272460938, 40.61391830444336], [-80.61175537109375, 40.619998931884766], [-80.57462310791016, 40.615909 [...]
-       ]
-      }
-     ],
-     "prompt_number": 8
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Then, we write the json to a file. "
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "with open('./example.json', 'w') as out:\n",
-      "    gj.dump(js, out)"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [],
-     "prompt_number": 9
-    },
-    {
-     "cell_type": "heading",
-     "level": 1,
-     "metadata": {},
-     "source": [
-      "Mapping"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Let's look at the columns that we're going to map."
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "js.features[0].properties.keys()[0:5]"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 11,
-       "text": [
-        "['HR90', 'PS90', 'FH80', 'HC60', 'FP79']"
-       ]
-      }
-     ],
-     "prompt_number": 11
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "We can map these attributes by calling them as commands to the choropleth mapping function. \n",
-      "\n",
-      "Here's the simplest mapping command:"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "fm.choropleth_map('./example.json', 'FIPS', 'HR90')"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "html": [
-        "<iframe srcdoc=\"<!DOCTYPE html>\n",
-        "<head>\n",
-        "   <meta http-equiv="content-type" content="text/html; charset=UTF-8" />\n",
-        "   <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />\n",
-        "   <script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>\n",
-        "   <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>\n",
-        "   <script src="http://d3js.org/queue.v1.min.js"></script>\n",
-        "\n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "\n",
-        "   <style>\n",
-        "\n",
-        "      html, body {\n",
-        "        width: 100%;\n",
-        "        height: 100%;\n",
-        "        margin: 0;\n",
-        "        padding: 0;\n",
-        "      }\n",
-        "\n",
-        "      .legend {\n",
-        "          padding: 0px 0px;\n",
-        "          font: 10px sans-serif;\n",
-        "          background: white;\n",
-        "          background: rgba(255,255,255,0.8);\n",
-        "          box-shadow: 0 0 15px rgba(0,0,0,0.2);\n",
-        "          border-radius: 5px;\n",
-        "      }\n",
-        "\n",
-        "      .key path {\n",
-        "        display: none;\n",
-        "      }\n",
-        "\n",
-        "   </style>\n",
-        "</head>\n",
-        "\n",
-        "<body>\n",
-        "\n",
-        "   <div id="map" style="width: 960px; height: 500px"></div>\n",
-        "\n",
-        "   <script>\n",
-        "\n",
-        "      queue()\n",
-        "          .defer(d3.json, 'data.json')\n",
-        "          .defer(d3.json, './example.json')\n",
-        "          .await(makeMap)\n",
-        "\n",
-        "      function makeMap(error, data_1,gjson_1) {\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          function matchKey(datapoint, key_variable){\n",
-        "              if (typeof key_variable[0][datapoint] === 'undefined') {\n",
-        "                  return null;\n",
-        "              }\n",
-        "              else {\n",
-        "                  return parseFloat(key_variable[0][datapoint]);\n",
-        "              };\n",
-        "          };\n",
-        "\n",
-        "          \n",
-        "          var color = d3.scale.threshold()\n",
-        "              .domain([4.1795187812600005, 6.904210850319998, 9.783181576819999, 14.2778818492])\n",
-        "              .range(['#FFFFCC', '#D9F0A3', '#ADDD8E', '#78C679', '#41AB5D']);\n",
-        "          \n",
-        "\n",
-        "          var map = L.map('map').setView([32.7965526581, -90.8477554321], 5);\n",
-        "\n",
-        "          L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {\n",
-        "              maxZoom: 18,\n",
-        "              attribution: 'Map data (c) <a href="http://openstreetmap.org">OpenStreetMap</a> contributors'\n",
-        "          }).addTo(map);\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          function style_1(feature) {\n",
-        "    return {\n",
-        "        fillColor: color(matchKey(feature.properties.FIPS, data_1)),\n",
-        "        weight: 1,\n",
-        "        opacity: 0.2,\n",
-        "        color: 'black',\n",
-        "        fillOpacity: 0.5\n",
-        "    };\n",
-        "}\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          gJson_layer_1 = L.geoJson(gjson_1, {style: style_1}).addTo(map)\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "              var legend = L.control({position: 'topright'});\n",
-        "\n",
-        "    legend.onAdd = function (map) {var div = L.DomUtil.create('div', 'legend'); return div};\n",
-        "\n",
-        "    legend.addTo(map);\n",
-        "\n",
-        "    var x = d3.scale.linear()\n",
-        "    .domain([0, 15])\n",
-        "    .range([0, 400]);\n",
-        "\n",
-        "    var xAxis = d3.svg.axis()\n",
-        "        .scale(x)\n",
-        "        .orient("top")\n",
-        "        .tickSize(1)\n",
-        "        .tickValues([4.1795187812600005, 6.904210850319998, 9.783181576819999, 14.2778818492]);\n",
-        "\n",
-        "    var svg = d3.select(".legend.leaflet-control").append("svg")\n",
-        "        .attr("id", 'legend')\n",
-        "        .attr("width", 450)\n",
-        "        .attr("height", 40);\n",
-        "\n",
-        "    var g = svg.append("g")\n",
-        "        .attr("class", "key")\n",
-        "        .attr("transform", "translate(25,16)");\n",
-        "\n",
-        "    g.selectAll("rect")\n",
-        "        .data(color.range().map(function(d, i) {\n",
-        "          return {\n",
-        "            x0: i ? x(color.domain()[i - 1]) : x.range()[0],\n",
-        "            x1: i < color.domain().length ? x(color.domain()[i]) : x.range()[1],\n",
-        "            z: d\n",
-        "          };\n",
-        "        }))\n",
-        "      .enter().append("rect")\n",
-        "        .attr("height", 10)\n",
-        "        .attr("x", function(d) { return d.x0; })\n",
-        "        .attr("width", function(d) { return d.x1 - d.x0; })\n",
-        "        .style("fill", function(d) { return d.z; });\n",
-        "\n",
-        "    g.call(xAxis).append("text")\n",
-        "        .attr("class", "caption")\n",
-        "        .attr("y", 21)\n",
-        "        .text('HR90');\n",
-        "          \n",
-        "\n",
-        "      };\n",
-        "\n",
-        "   </script>\n",
-        "</body>\" style=\"width: 100%; height: 510px; border: none\"></iframe>"
-       ],
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 12,
-       "text": [
-        "<IPython.core.display.HTML at 0x7f53fddcc150>"
-       ]
-      }
-     ],
-     "prompt_number": 12
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "This produces a map using default classifications and color schemes and saves it to an html file. We set the function to have sane defaults. However, if the user wants to have more control, we have many options available. "
-     ]
-    },
-    {
-     "cell_type": "heading",
-     "level": 3,
-     "metadata": {},
-     "source": [
-      "There are arguments to change classification scheme:"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "fm.choropleth_map('./example.json', 'FIPS', 'HR90', classification = 'Quantiles')"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "html": [
-        "<iframe srcdoc=\"<!DOCTYPE html>\n",
-        "<head>\n",
-        "   <meta http-equiv="content-type" content="text/html; charset=UTF-8" />\n",
-        "   <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />\n",
-        "   <script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>\n",
-        "   <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>\n",
-        "   <script src="http://d3js.org/queue.v1.min.js"></script>\n",
-        "\n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "\n",
-        "   <style>\n",
-        "\n",
-        "      html, body {\n",
-        "        width: 100%;\n",
-        "        height: 100%;\n",
-        "        margin: 0;\n",
-        "        padding: 0;\n",
-        "      }\n",
-        "\n",
-        "      .legend {\n",
-        "          padding: 0px 0px;\n",
-        "          font: 10px sans-serif;\n",
-        "          background: white;\n",
-        "          background: rgba(255,255,255,0.8);\n",
-        "          box-shadow: 0 0 15px rgba(0,0,0,0.2);\n",
-        "          border-radius: 5px;\n",
-        "      }\n",
-        "\n",
-        "      .key path {\n",
-        "        display: none;\n",
-        "      }\n",
-        "\n",
-        "   </style>\n",
-        "</head>\n",
-        "\n",
-        "<body>\n",
-        "\n",
-        "   <div id="map" style="width: 960px; height: 500px"></div>\n",
-        "\n",
-        "   <script>\n",
-        "\n",
-        "      queue()\n",
-        "          .defer(d3.json, 'data.json')\n",
-        "          .defer(d3.json, './example.json')\n",
-        "          .await(makeMap)\n",
-        "\n",
-        "      function makeMap(error, data_1,gjson_1) {\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          function matchKey(datapoint, key_variable){\n",
-        "              if (typeof key_variable[0][datapoint] === 'undefined') {\n",
-        "                  return null;\n",
-        "              }\n",
-        "              else {\n",
-        "                  return parseFloat(key_variable[0][datapoint]);\n",
-        "              };\n",
-        "          };\n",
-        "\n",
-        "          \n",
-        "          var color = d3.scale.threshold()\n",
-        "              .domain([4.1795187812600005, 6.904210850319998, 9.783181576819999, 14.2778818492])\n",
-        "              .range(['#FFFFCC', '#D9F0A3', '#ADDD8E', '#78C679', '#41AB5D']);\n",
-        "          \n",
-        "\n",
-        "          var map = L.map('map').setView([32.7965526581, -90.8477554321], 5);\n",
-        "\n",
-        "          L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {\n",
-        "              maxZoom: 18,\n",
-        "              attribution: 'Map data (c) <a href="http://openstreetmap.org">OpenStreetMap</a> contributors'\n",
-        "          }).addTo(map);\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          function style_1(feature) {\n",
-        "    return {\n",
-        "        fillColor: color(matchKey(feature.properties.FIPS, data_1)),\n",
-        "        weight: 1,\n",
-        "        opacity: 0.2,\n",
-        "        color: 'black',\n",
-        "        fillOpacity: 0.5\n",
-        "    };\n",
-        "}\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          gJson_layer_1 = L.geoJson(gjson_1, {style: style_1}).addTo(map)\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "              var legend = L.control({position: 'topright'});\n",
-        "\n",
-        "    legend.onAdd = function (map) {var div = L.DomUtil.create('div', 'legend'); return div};\n",
-        "\n",
-        "    legend.addTo(map);\n",
-        "\n",
-        "    var x = d3.scale.linear()\n",
-        "    .domain([0, 15])\n",
-        "    .range([0, 400]);\n",
-        "\n",
-        "    var xAxis = d3.svg.axis()\n",
-        "        .scale(x)\n",
-        "        .orient("top")\n",
-        "        .tickSize(1)\n",
-        "        .tickValues([4.1795187812600005, 6.904210850319998, 9.783181576819999, 14.2778818492]);\n",
-        "\n",
-        "    var svg = d3.select(".legend.leaflet-control").append("svg")\n",
-        "        .attr("id", 'legend')\n",
-        "        .attr("width", 450)\n",
-        "        .attr("height", 40);\n",
-        "\n",
-        "    var g = svg.append("g")\n",
-        "        .attr("class", "key")\n",
-        "        .attr("transform", "translate(25,16)");\n",
-        "\n",
-        "    g.selectAll("rect")\n",
-        "        .data(color.range().map(function(d, i) {\n",
-        "          return {\n",
-        "            x0: i ? x(color.domain()[i - 1]) : x.range()[0],\n",
-        "            x1: i < color.domain().length ? x(color.domain()[i]) : x.range()[1],\n",
-        "            z: d\n",
-        "          };\n",
-        "        }))\n",
-        "      .enter().append("rect")\n",
-        "        .attr("height", 10)\n",
-        "        .attr("x", function(d) { return d.x0; })\n",
-        "        .attr("width", function(d) { return d.x1 - d.x0; })\n",
-        "        .style("fill", function(d) { return d.z; });\n",
-        "\n",
-        "    g.call(xAxis).append("text")\n",
-        "        .attr("class", "caption")\n",
-        "        .attr("y", 21)\n",
-        "        .text('HR90');\n",
-        "          \n",
-        "\n",
-        "      };\n",
-        "\n",
-        "   </script>\n",
-        "</body>\" style=\"width: 100%; height: 510px; border: none\"></iframe>"
-       ],
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 13,
-       "text": [
-        "<IPython.core.display.HTML at 0x7f53fddcc9d0>"
-       ]
-      }
-     ],
-     "prompt_number": 13
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Most `PySAL` classifiers are supprorted. "
-     ]
-    },
-    {
-     "cell_type": "heading",
-     "level": 2,
-     "metadata": {},
-     "source": [
-      "Base Map Type"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "fm.choropleth_map('./example.json', 'FIPS', 'HR90', classification = 'Jenks Caspall', tiles='Stamen Toner', save=True) "
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "html": [
-        "<iframe srcdoc=\"<!DOCTYPE html>\n",
-        "<head>\n",
-        "   <meta http-equiv="content-type" content="text/html; charset=UTF-8" />\n",
-        "   <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />\n",
-        "   <script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>\n",
-        "   <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>\n",
-        "   <script src="http://d3js.org/queue.v1.min.js"></script>\n",
-        "\n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "\n",
-        "   <style>\n",
-        "\n",
-        "      html, body {\n",
-        "        width: 100%;\n",
-        "        height: 100%;\n",
-        "        margin: 0;\n",
-        "        padding: 0;\n",
-        "      }\n",
-        "\n",
-        "      .legend {\n",
-        "          padding: 0px 0px;\n",
-        "          font: 10px sans-serif;\n",
-        "          background: white;\n",
-        "          background: rgba(255,255,255,0.8);\n",
-        "          box-shadow: 0 0 15px rgba(0,0,0,0.2);\n",
-        "          border-radius: 5px;\n",
-        "      }\n",
-        "\n",
-        "      .key path {\n",
-        "        display: none;\n",
-        "      }\n",
-        "\n",
-        "   </style>\n",
-        "</head>\n",
-        "\n",
-        "<body>\n",
-        "\n",
-        "   <div id="map" style="width: 960px; height: 500px"></div>\n",
-        "\n",
-        "   <script>\n",
-        "\n",
-        "      queue()\n",
-        "          .defer(d3.json, 'data.json')\n",
-        "          .defer(d3.json, './example.json')\n",
-        "          .await(makeMap)\n",
-        "\n",
-        "      function makeMap(error, data_1,gjson_1) {\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          function matchKey(datapoint, key_variable){\n",
-        "              if (typeof key_variable[0][datapoint] === 'undefined') {\n",
-        "                  return null;\n",
-        "              }\n",
-        "              else {\n",
-        "                  return parseFloat(key_variable[0][datapoint]);\n",
-        "              };\n",
-        "          };\n",
-        "\n",
-        "          \n",
-        "          var color = d3.scale.threshold()\n",
-        "              .domain([2.3886301206, 6.7263065851, 11.080639353, 17.516035134])\n",
-        "              .range(['#FFFFCC', '#D9F0A3', '#ADDD8E', '#78C679', '#41AB5D']);\n",
-        "          \n",
-        "\n",
-        "          var map = L.map('map').setView([32.7965526581, -90.8477554321], 5);\n",
-        "\n",
-        "          L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.jpg', {\n",
-        "              maxZoom: 18,\n",
-        "              attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>.'\n",
-        "          }).addTo(map);\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          function style_1(feature) {\n",
-        "    return {\n",
-        "        fillColor: color(matchKey(feature.properties.FIPS, data_1)),\n",
-        "        weight: 1,\n",
-        "        opacity: 0.2,\n",
-        "        color: 'black',\n",
-        "        fillOpacity: 0.5\n",
-        "    };\n",
-        "}\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          gJson_layer_1 = L.geoJson(gjson_1, {style: style_1}).addTo(map)\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "              var legend = L.control({position: 'topright'});\n",
-        "\n",
-        "    legend.onAdd = function (map) {var div = L.DomUtil.create('div', 'legend'); return div};\n",
-        "\n",
-        "    legend.addTo(map);\n",
-        "\n",
-        "    var x = d3.scale.linear()\n",
-        "    .domain([0, 19])\n",
-        "    .range([0, 400]);\n",
-        "\n",
-        "    var xAxis = d3.svg.axis()\n",
-        "        .scale(x)\n",
-        "        .orient("top")\n",
-        "        .tickSize(1)\n",
-        "        .tickValues([2.3886301206, 6.7263065851, 11.080639353, 17.516035134]);\n",
-        "\n",
-        "    var svg = d3.select(".legend.leaflet-control").append("svg")\n",
-        "        .attr("id", 'legend')\n",
-        "        .attr("width", 450)\n",
-        "        .attr("height", 40);\n",
-        "\n",
-        "    var g = svg.append("g")\n",
-        "        .attr("class", "key")\n",
-        "        .attr("transform", "translate(25,16)");\n",
-        "\n",
-        "    g.selectAll("rect")\n",
-        "        .data(color.range().map(function(d, i) {\n",
-        "          return {\n",
-        "            x0: i ? x(color.domain()[i - 1]) : x.range()[0],\n",
-        "            x1: i < color.domain().length ? x(color.domain()[i]) : x.range()[1],\n",
-        "            z: d\n",
-        "          };\n",
-        "        }))\n",
-        "      .enter().append("rect")\n",
-        "        .attr("height", 10)\n",
-        "        .attr("x", function(d) { return d.x0; })\n",
-        "        .attr("width", function(d) { return d.x1 - d.x0; })\n",
-        "        .style("fill", function(d) { return d.z; });\n",
-        "\n",
-        "    g.call(xAxis).append("text")\n",
-        "        .attr("class", "caption")\n",
-        "        .attr("y", 21)\n",
-        "        .text('HR90');\n",
-        "          \n",
-        "\n",
-        "      };\n",
-        "\n",
-        "   </script>\n",
-        "</body>\" style=\"width: 100%; height: 510px; border: none\"></iframe>"
-       ],
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 15,
-       "text": [
-        "<IPython.core.display.HTML at 0x7f53fddcc090>"
-       ]
-      }
-     ],
-     "prompt_number": 15
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "We support the entire range of builtin basemap types in Folium, but custom tilesets from MapBox are not supported yet. "
-     ]
-    },
-    {
-     "cell_type": "heading",
-     "level": 2,
-     "metadata": {},
-     "source": [
-      "Color Scheme"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "fm.choropleth_map('./example.json', 'FIPS', 'HR80', classification = 'Jenks Caspall', tiles='Stamen Toner', fill_color = 'PuBuGn', save=True) "
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "html": [
-        "<iframe srcdoc=\"<!DOCTYPE html>\n",
-        "<head>\n",
-        "   <meta http-equiv="content-type" content="text/html; charset=UTF-8" />\n",
-        "   <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />\n",
-        "   <script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>\n",
-        "   <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>\n",
-        "   <script src="http://d3js.org/queue.v1.min.js"></script>\n",
-        "\n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "\n",
-        "   <style>\n",
-        "\n",
-        "      html, body {\n",
-        "        width: 100%;\n",
-        "        height: 100%;\n",
-        "        margin: 0;\n",
-        "        padding: 0;\n",
-        "      }\n",
-        "\n",
-        "      .legend {\n",
-        "          padding: 0px 0px;\n",
-        "          font: 10px sans-serif;\n",
-        "          background: white;\n",
-        "          background: rgba(255,255,255,0.8);\n",
-        "          box-shadow: 0 0 15px rgba(0,0,0,0.2);\n",
-        "          border-radius: 5px;\n",
-        "      }\n",
-        "\n",
-        "      .key path {\n",
-        "        display: none;\n",
-        "      }\n",
-        "\n",
-        "   </style>\n",
-        "</head>\n",
-        "\n",
-        "<body>\n",
-        "\n",
-        "   <div id="map" style="width: 960px; height: 500px"></div>\n",
-        "\n",
-        "   <script>\n",
-        "\n",
-        "      queue()\n",
-        "          .defer(d3.json, 'data.json')\n",
-        "          .defer(d3.json, './example.json')\n",
-        "          .await(makeMap)\n",
-        "\n",
-        "      function makeMap(error, data_1,gjson_1) {\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          function matchKey(datapoint, key_variable){\n",
-        "              if (typeof key_variable[0][datapoint] === 'undefined') {\n",
-        "                  return null;\n",
-        "              }\n",
-        "              else {\n",
-        "                  return parseFloat(key_variable[0][datapoint]);\n",
-        "              };\n",
-        "          };\n",
-        "\n",
-        "          \n",
-        "          var color = d3.scale.threshold()\n",
-        "              .domain([5.0172467858, 8.749365671, 12.657426745, 17.721070353])\n",
-        "              .range(['#F6EFF7', '#D0D1E6', '#A6BDDB', '#67A9CF', '#3690C0']);\n",
-        "          \n",
-        "\n",
-        "          var map = L.map('map').setView([32.7965526581, -90.8477554321], 5);\n",
-        "\n",
-        "          L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.jpg', {\n",
-        "              maxZoom: 18,\n",
-        "              attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>.'\n",
-        "          }).addTo(map);\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          function style_1(feature) {\n",
-        "    return {\n",
-        "        fillColor: color(matchKey(feature.properties.FIPS, data_1)),\n",
-        "        weight: 1,\n",
-        "        opacity: 0.2,\n",
-        "        color: 'black',\n",
-        "        fillOpacity: 0.5\n",
-        "    };\n",
-        "}\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          gJson_layer_1 = L.geoJson(gjson_1, {style: style_1}).addTo(map)\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "              var legend = L.control({position: 'topright'});\n",
-        "\n",
-        "    legend.onAdd = function (map) {var div = L.DomUtil.create('div', 'legend'); return div};\n",
-        "\n",
-        "    legend.addTo(map);\n",
-        "\n",
-        "    var x = d3.scale.linear()\n",
-        "    .domain([0, 19])\n",
-        "    .range([0, 400]);\n",
-        "\n",
-        "    var xAxis = d3.svg.axis()\n",
-        "        .scale(x)\n",
-        "        .orient("top")\n",
-        "        .tickSize(1)\n",
-        "        .tickValues([5.0172467858, 8.749365671, 12.657426745, 17.721070353]);\n",
-        "\n",
-        "    var svg = d3.select(".legend.leaflet-control").append("svg")\n",
-        "        .attr("id", 'legend')\n",
-        "        .attr("width", 450)\n",
-        "        .attr("height", 40);\n",
-        "\n",
-        "    var g = svg.append("g")\n",
-        "        .attr("class", "key")\n",
-        "        .attr("transform", "translate(25,16)");\n",
-        "\n",
-        "    g.selectAll("rect")\n",
-        "        .data(color.range().map(function(d, i) {\n",
-        "          return {\n",
-        "            x0: i ? x(color.domain()[i - 1]) : x.range()[0],\n",
-        "            x1: i < color.domain().length ? x(color.domain()[i]) : x.range()[1],\n",
-        "            z: d\n",
-        "          };\n",
-        "        }))\n",
-        "      .enter().append("rect")\n",
-        "        .attr("height", 10)\n",
-        "        .attr("x", function(d) { return d.x0; })\n",
-        "        .attr("width", function(d) { return d.x1 - d.x0; })\n",
-        "        .style("fill", function(d) { return d.z; });\n",
-        "\n",
-        "    g.call(xAxis).append("text")\n",
-        "        .attr("class", "caption")\n",
-        "        .attr("y", 21)\n",
-        "        .text('HR80');\n",
-        "          \n",
-        "\n",
-        "      };\n",
-        "\n",
-        "   </script>\n",
-        "</body>\" style=\"width: 100%; height: 510px; border: none\"></iframe>"
-       ],
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 16,
-       "text": [
-        "<IPython.core.display.HTML at 0x7f53fcb72590>"
-       ]
-      }
-     ],
-     "prompt_number": 16
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "All color schemes are [Color Brewer](https://colorbrewer2.org) and simply pass through to `Folium` on execution. "
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Mapping using Pysal and Folium: Example"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "import pysal as ps\n",
+    "import geojson as gj\n",
+    "import folium_mapping as fm"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "First, we need to convert the data into a JSON format. JSON, short for \"Javascript Serialized Object Notation,\" is a simple and effective way to represent objects in a digital environment. For geographic information, the [GeoJSON](https://geojson.org) standard defines how to represent geographic information in JSON format. Python programmers may be more comfortable thinking of JSON data as something akin to a standard Python dictionary. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "filepath = ps.examples.get_path('south.shp')[:-4]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "shp = ps.open(filepath + '.shp')\n",
+    "dbf = ps.open(filepath + '.dbf')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "js = fm.build_features(shp, dbf)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Just to show, this constructs a dictionary with the following keys:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "dict_keys(['bbox', 'features', 'type'])"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "js.keys()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'FeatureCollection'"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "js.type"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[-106.6495132446289, 24.95596694946289, -75.0459976196289, 40.63713836669922]"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "js.bbox"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{\"bbox\": [-80.6688232421875, 40.39815902709961, -80.52220916748047, 40.63713836669922], \"geometry\": {\"coordinates\": [[[-80.6280517578125, 40.39815902709961], [-80.60203552246094, 40.480472564697266], [-80.62545776367188, 40.504398345947266], [-80.6336441040039, 40.53913879394531], [-80.6688232421875, 40.568214416503906], [-80.66793060302734, 40.58207321166992], [-80.63754272460938, 40.61391830444336], [-80.61175537109375, 40.619998931884766], [-80.57462310791016, 40.6159095 [...]
+      ]
+     },
+     "execution_count": 8,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "js.features[0]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Then, we write the json to a file. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "with open('./example.json', 'w') as out:\n",
+    "    gj.dump(js, out)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Mapping"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's look at the columns that we're going to map."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "['POL80', 'PO70', 'UE70', 'PO80', 'BLK80']"
+      ]
+     },
+     "execution_count": 10,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "#js.features[0].properties.keys()[0:5]\n",
+    "list(js.features[0].properties.keys())[:5]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We can map these attributes by calling them as commands to the choropleth mapping function. \n",
+    "\n",
+    "Here's the simplest mapping command:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "import folium as fol"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "fol.Map.choropleth?"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/ljw/.local/lib/python3.5/site-packages/folium/folium.py:504: UserWarning: This method is deprecated. Please use Map.choropleth instead.\n",
+      "  warnings.warn('This method is deprecated. '\n"
      ]
     },
     {
-     "cell_type": "heading",
-     "level": 2,
+     "data": {
+      "text/html": [
+       "<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"data:text/html;base64,CiAgICAgICAgPCFET0NUWVBFIGh0bWw+CiAgICAgICAgPGhlYWQ+CiAgICAgICAgICAgIAogICAgICAgIAogICAgICAgICAgICA8bWV0YSBodHRwLWVxdWl2PSJjb250ZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD1VVEYtOCIgLz4KICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgCiAgICAgICAgICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9sZWFmbGV0LzAuNy4zL2xlYWZsZXQu [...]
+      ],
+      "text/plain": [
+       "<folium.folium.Map at 0x7fdb350ff198>"
+      ]
+     },
+     "execution_count": 13,
      "metadata": {},
-     "source": [
-      "Class numbers"
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fm.choropleth_map('./example.json', 'FIPS', 'HR90')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/ljw/.local/lib/python3.5/site-packages/folium/folium.py:504: UserWarning: This method is deprecated. Please use Map.choropleth instead.\n",
+      "  warnings.warn('This method is deprecated. '\n"
      ]
     },
     {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "fm.choropleth_map('./example.json', 'FIPS', 'HR80', classification = 'Equal Interval', classes=6, tiles='Stamen Toner', fill_color='PuBuGn',save=True) "
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "html": [
-        "<iframe srcdoc=\"<!DOCTYPE html>\n",
-        "<head>\n",
-        "   <meta http-equiv="content-type" content="text/html; charset=UTF-8" />\n",
-        "   <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />\n",
-        "   <script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>\n",
-        "   <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>\n",
-        "   <script src="http://d3js.org/queue.v1.min.js"></script>\n",
-        "\n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "\n",
-        "   <style>\n",
-        "\n",
-        "      html, body {\n",
-        "        width: 100%;\n",
-        "        height: 100%;\n",
-        "        margin: 0;\n",
-        "        padding: 0;\n",
-        "      }\n",
-        "\n",
-        "      .legend {\n",
-        "          padding: 0px 0px;\n",
-        "          font: 10px sans-serif;\n",
-        "          background: white;\n",
-        "          background: rgba(255,255,255,0.8);\n",
-        "          box-shadow: 0 0 15px rgba(0,0,0,0.2);\n",
-        "          border-radius: 5px;\n",
-        "      }\n",
-        "\n",
-        "      .key path {\n",
-        "        display: none;\n",
-        "      }\n",
-        "\n",
-        "   </style>\n",
-        "</head>\n",
-        "\n",
-        "<body>\n",
-        "\n",
-        "   <div id="map" style="width: 960px; height: 500px"></div>\n",
-        "\n",
-        "   <script>\n",
-        "\n",
-        "      queue()\n",
-        "          .defer(d3.json, 'data.json')\n",
-        "          .defer(d3.json, './example.json')\n",
-        "          .await(makeMap)\n",
-        "\n",
-        "      function makeMap(error, data_1,gjson_1) {\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          function matchKey(datapoint, key_variable){\n",
-        "              if (typeof key_variable[0][datapoint] === 'undefined') {\n",
-        "                  return null;\n",
-        "              }\n",
-        "              else {\n",
-        "                  return parseFloat(key_variable[0][datapoint]);\n",
-        "              };\n",
-        "          };\n",
-        "\n",
-        "          \n",
-        "          var color = d3.scale.threshold()\n",
-        "              .domain([7.057234169166667, 14.114468338333333, 21.1717025075, 28.228936676666667, 35.28617084583333])\n",
-        "              .range(['#F6EFF7', '#D0D1E6', '#A6BDDB', '#67A9CF', '#3690C0', '#02818A']);\n",
-        "          \n",
-        "\n",
-        "          var map = L.map('map').setView([32.7965526581, -90.8477554321], 5);\n",
-        "\n",
-        "          L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.jpg', {\n",
-        "              maxZoom: 18,\n",
-        "              attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>.'\n",
-        "          }).addTo(map);\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          function style_1(feature) {\n",
-        "    return {\n",
-        "        fillColor: color(matchKey(feature.properties.FIPS, data_1)),\n",
-        "        weight: 1,\n",
-        "        opacity: 0.2,\n",
-        "        color: 'black',\n",
-        "        fillOpacity: 0.5\n",
-        "    };\n",
-        "}\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          gJson_layer_1 = L.geoJson(gjson_1, {style: style_1}).addTo(map)\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "              var legend = L.control({position: 'topright'});\n",
-        "\n",
-        "    legend.onAdd = function (map) {var div = L.DomUtil.create('div', 'legend'); return div};\n",
-        "\n",
-        "    legend.addTo(map);\n",
-        "\n",
-        "    var x = d3.scale.linear()\n",
-        "    .domain([0, 38])\n",
-        "    .range([0, 400]);\n",
-        "\n",
-        "    var xAxis = d3.svg.axis()\n",
-        "        .scale(x)\n",
-        "        .orient("top")\n",
-        "        .tickSize(1)\n",
-        "        .tickValues([7.057234169166667, 14.114468338333333, 21.1717025075, 28.228936676666667, 35.28617084583333]);\n",
-        "\n",
-        "    var svg = d3.select(".legend.leaflet-control").append("svg")\n",
-        "        .attr("id", 'legend')\n",
-        "        .attr("width", 450)\n",
-        "        .attr("height", 40);\n",
-        "\n",
-        "    var g = svg.append("g")\n",
-        "        .attr("class", "key")\n",
-        "        .attr("transform", "translate(25,16)");\n",
-        "\n",
-        "    g.selectAll("rect")\n",
-        "        .data(color.range().map(function(d, i) {\n",
-        "          return {\n",
-        "            x0: i ? x(color.domain()[i - 1]) : x.range()[0],\n",
-        "            x1: i < color.domain().length ? x(color.domain()[i]) : x.range()[1],\n",
-        "            z: d\n",
-        "          };\n",
-        "        }))\n",
-        "      .enter().append("rect")\n",
-        "        .attr("height", 10)\n",
-        "        .attr("x", function(d) { return d.x0; })\n",
-        "        .attr("width", function(d) { return d.x1 - d.x0; })\n",
-        "        .style("fill", function(d) { return d.z; });\n",
-        "\n",
-        "    g.call(xAxis).append("text")\n",
-        "        .attr("class", "caption")\n",
-        "        .attr("y", 21)\n",
-        "        .text('HR80');\n",
-        "          \n",
-        "\n",
-        "      };\n",
-        "\n",
-        "   </script>\n",
-        "</body>\" style=\"width: 100%; height: 510px; border: none\"></iframe>"
-       ],
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 18,
-       "text": [
-        "<IPython.core.display.HTML at 0x7f53fcb729d0>"
-       ]
-      }
-     ],
-     "prompt_number": 18
-    },
-    {
-     "cell_type": "markdown",
+     "data": {
+      "text/html": [
+       "<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"data:text/html;base64,CiAgICAgICAgPCFET0NUWVBFIGh0bWw+CiAgICAgICAgPGhlYWQ+CiAgICAgICAgICAgIAogICAgICAgIAogICAgICAgICAgICA8bWV0YSBodHRwLWVxdWl2PSJjb250ZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD1VVEYtOCIgLz4KICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgCiAgICAgICAgICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9sZWFmbGV0LzAuNy4zL2xlYWZsZXQu [...]
+      ],
+      "text/plain": [
+       "<folium.folium.Map at 0x7fdb33932c50>"
+      ]
+     },
+     "execution_count": 14,
      "metadata": {},
-     "source": [
-      "Folium supports up to 6 classes."
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fm.choropleth_map('./example.json', 'FIPS', 'HR90')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This produces a map using default classifications and color schemes and saves it to an html file. We set the function to have sane defaults. However, if the user wants to have more control, we have many options available. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### There are arguments to change classification scheme:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/ljw/.local/lib/python3.5/site-packages/folium/folium.py:504: UserWarning: This method is deprecated. Please use Map.choropleth instead.\n",
+      "  warnings.warn('This method is deprecated. '\n"
      ]
     },
     {
-     "cell_type": "heading",
-     "level": 2,
+     "data": {
+      "text/html": [
+       "<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"data:text/html;base64,CiAgICAgICAgPCFET0NUWVBFIGh0bWw+CiAgICAgICAgPGhlYWQ+CiAgICAgICAgICAgIAogICAgICAgIAogICAgICAgICAgICA8bWV0YSBodHRwLWVxdWl2PSJjb250ZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD1VVEYtOCIgLz4KICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgCiAgICAgICAgICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9sZWFmbGV0LzAuNy4zL2xlYWZsZXQu [...]
+      ],
+      "text/plain": [
+       "<folium.folium.Map at 0x7fdb31931c88>"
+      ]
+     },
+     "execution_count": 15,
      "metadata": {},
-     "source": [
-      "Standardization"
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fm.choropleth_map('./example.json', 'FIPS', 'HR90', classification = 'Quantiles')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Most `PySAL` classifiers are supprorted. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Base Map Type"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/ljw/.local/lib/python3.5/site-packages/folium/folium.py:504: UserWarning: This method is deprecated. Please use Map.choropleth instead.\n",
+      "  warnings.warn('This method is deprecated. '\n"
      ]
     },
     {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "fm.choropleth_map('./example.json', 'FIPS', 'HR90', classification = 'Quantiles', std='HR80' , tiles='Stamen Toner', fill_color='PuBuGn',save=True) "
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "html": [
-        "<iframe srcdoc=\"<!DOCTYPE html>\n",
-        "<head>\n",
-        "   <meta http-equiv="content-type" content="text/html; charset=UTF-8" />\n",
-        "   <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />\n",
-        "   <script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>\n",
-        "   <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>\n",
-        "   <script src="http://d3js.org/queue.v1.min.js"></script>\n",
-        "\n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "\n",
-        "   <style>\n",
-        "\n",
-        "      html, body {\n",
-        "        width: 100%;\n",
-        "        height: 100%;\n",
-        "        margin: 0;\n",
-        "        padding: 0;\n",
-        "      }\n",
-        "\n",
-        "      .legend {\n",
-        "          padding: 0px 0px;\n",
-        "          font: 10px sans-serif;\n",
-        "          background: white;\n",
-        "          background: rgba(255,255,255,0.8);\n",
-        "          box-shadow: 0 0 15px rgba(0,0,0,0.2);\n",
-        "          border-radius: 5px;\n",
-        "      }\n",
-        "\n",
-        "      .key path {\n",
-        "        display: none;\n",
-        "      }\n",
-        "\n",
-        "   </style>\n",
-        "</head>\n",
-        "\n",
-        "<body>\n",
-        "\n",
-        "   <div id="map" style="width: 960px; height: 500px"></div>\n",
-        "\n",
-        "   <script>\n",
-        "\n",
-        "      queue()\n",
-        "          .defer(d3.json, 'data.json')\n",
-        "          .defer(d3.json, './example.json')\n",
-        "          .await(makeMap)\n",
-        "\n",
-        "      function makeMap(error, data_1,gjson_1) {\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          function matchKey(datapoint, key_variable){\n",
-        "              if (typeof key_variable[0][datapoint] === 'undefined') {\n",
-        "                  return null;\n",
-        "              }\n",
-        "              else {\n",
-        "                  return parseFloat(key_variable[0][datapoint]);\n",
-        "              };\n",
-        "          };\n",
-        "\n",
-        "          \n",
-        "          var color = d3.scale.threshold()\n",
-        "              .domain([0.5185943291795233, 0.7967324987903716, 1.0780871918834456, 1.7476117844514245])\n",
-        "              .range(['#F6EFF7', '#D0D1E6', '#A6BDDB', '#67A9CF', '#3690C0']);\n",
-        "          \n",
-        "\n",
-        "          var map = L.map('map').setView([32.7965526581, -90.8477554321], 5);\n",
-        "\n",
-        "          L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.jpg', {\n",
-        "              maxZoom: 18,\n",
-        "              attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>.'\n",
-        "          }).addTo(map);\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          function style_1(feature) {\n",
-        "    return {\n",
-        "        fillColor: color(matchKey(feature.properties.FIPS, data_1)),\n",
-        "        weight: 1,\n",
-        "        opacity: 0.2,\n",
-        "        color: 'black',\n",
-        "        fillOpacity: 0.5\n",
-        "    };\n",
-        "}\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          gJson_layer_1 = L.geoJson(gjson_1, {style: style_1}).addTo(map)\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "              var legend = L.control({position: 'topright'});\n",
-        "\n",
-        "    legend.onAdd = function (map) {var div = L.DomUtil.create('div', 'legend'); return div};\n",
-        "\n",
-        "    legend.addTo(map);\n",
-        "\n",
-        "    var x = d3.scale.linear()\n",
-        "    .domain([0, 1])\n",
-        "    .range([0, 400]);\n",
-        "\n",
-        "    var xAxis = d3.svg.axis()\n",
-        "        .scale(x)\n",
-        "        .orient("top")\n",
-        "        .tickSize(1)\n",
-        "        .tickValues([0.5185943291795233, 0.7967324987903716, 1.0780871918834456, 1.7476117844514245]);\n",
-        "\n",
-        "    var svg = d3.select(".legend.leaflet-control").append("svg")\n",
-        "        .attr("id", 'legend')\n",
-        "        .attr("width", 450)\n",
-        "        .attr("height", 40);\n",
-        "\n",
-        "    var g = svg.append("g")\n",
-        "        .attr("class", "key")\n",
-        "        .attr("transform", "translate(25,16)");\n",
-        "\n",
-        "    g.selectAll("rect")\n",
-        "        .data(color.range().map(function(d, i) {\n",
-        "          return {\n",
-        "            x0: i ? x(color.domain()[i - 1]) : x.range()[0],\n",
-        "            x1: i < color.domain().length ? x(color.domain()[i]) : x.range()[1],\n",
-        "            z: d\n",
-        "          };\n",
-        "        }))\n",
-        "      .enter().append("rect")\n",
-        "        .attr("height", 10)\n",
-        "        .attr("x", function(d) { return d.x0; })\n",
-        "        .attr("width", function(d) { return d.x1 - d.x0; })\n",
-        "        .style("fill", function(d) { return d.z; });\n",
-        "\n",
-        "    g.call(xAxis).append("text")\n",
-        "        .attr("class", "caption")\n",
-        "        .attr("y", 21)\n",
-        "        .text('HR90');\n",
-        "          \n",
-        "\n",
-        "      };\n",
-        "\n",
-        "   </script>\n",
-        "</body>\" style=\"width: 100%; height: 510px; border: none\"></iframe>"
-       ],
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 19,
-       "text": [
-        "<IPython.core.display.HTML at 0x7f53fcb72a10>"
-       ]
-      }
-     ],
-     "prompt_number": 19
-    },
-    {
-     "cell_type": "markdown",
+     "data": {
+      "text/html": [
+       "<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"data:text/html;base64,CiAgICAgICAgPCFET0NUWVBFIGh0bWw+CiAgICAgICAgPGhlYWQ+CiAgICAgICAgICAgIAogICAgICAgIAogICAgICAgICAgICA8bWV0YSBodHRwLWVxdWl2PSJjb250ZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD1VVEYtOCIgLz4KICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgCiAgICAgICAgICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9sZWFmbGV0LzAuNy4zL2xlYWZsZXQu [...]
+      ],
+      "text/plain": [
+       "<folium.folium.Map at 0x7fdb3113ea90>"
+      ]
+     },
+     "execution_count": 16,
      "metadata": {},
-     "source": [
-      "The goal of the `std` argument was to implement a very flexible transformation argument that could take functions, fields, and static items. As of now, it only supports float/int scaling and division by another field, which would support the mapping of spatial rates or population rates."
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fm.choropleth_map('./example.json', 'FIPS', 'HR90', classification = 'Jenks Caspall', tiles='Stamen Toner', save=True) "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We support the entire range of builtin basemap types in Folium, but custom tilesets from MapBox are not supported yet. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Color Scheme"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/ljw/.local/lib/python3.5/site-packages/folium/folium.py:504: UserWarning: This method is deprecated. Please use Map.choropleth instead.\n",
+      "  warnings.warn('This method is deprecated. '\n"
      ]
     },
     {
-     "cell_type": "heading",
-     "level": 1,
+     "data": {
+      "text/html": [
+       "<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"data:text/html;base64,CiAgICAgICAgPCFET0NUWVBFIGh0bWw+CiAgICAgICAgPGhlYWQ+CiAgICAgICAgICAgIAogICAgICAgIAogICAgICAgICAgICA8bWV0YSBodHRwLWVxdWl2PSJjb250ZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD1VVEYtOCIgLz4KICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgCiAgICAgICAgICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9sZWFmbGV0LzAuNy4zL2xlYWZsZXQu [...]
+      ],
+      "text/plain": [
+       "<folium.folium.Map at 0x7fdb3421c9b0>"
+      ]
+     },
+     "execution_count": 17,
      "metadata": {},
-     "source": [
-      "Conclusion"
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fm.choropleth_map('./example.json', 'FIPS', 'HR80', classification = 'Jenks Caspall', tiles='Stamen Toner', fill_color = 'PuBuGn', save=True) "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "All color schemes are [Color Brewer](https://colorbrewer2.org) and simply pass through to `Folium` on execution. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Class numbers"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/ljw/.local/lib/python3.5/site-packages/folium/folium.py:504: UserWarning: This method is deprecated. Please use Map.choropleth instead.\n",
+      "  warnings.warn('This method is deprecated. '\n"
      ]
     },
     {
-     "cell_type": "markdown",
+     "data": {
+      "text/html": [
+       "<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"data:text/html;base64,CiAgICAgICAgPCFET0NUWVBFIGh0bWw+CiAgICAgICAgPGhlYWQ+CiAgICAgICAgICAgIAogICAgICAgIAogICAgICAgICAgICA8bWV0YSBodHRwLWVxdWl2PSJjb250ZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD1VVEYtOCIgLz4KICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgCiAgICAgICAgICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9sZWFmbGV0LzAuNy4zL2xlYWZsZXQu [...]
+      ],
+      "text/plain": [
+       "<folium.folium.Map at 0x7fdb34200ac8>"
+      ]
+     },
+     "execution_count": 18,
      "metadata": {},
-     "source": [
-      "Overall, the function neatly encapsulates three components:\n",
-      "\n",
-      "- plumbing large lists of arguments together to simplify mapping from JSON files\n",
-      "- searching for sane defaults (bounding box & zoom level)\n",
-      "- implementing classification and standardization useful for choropleth mapping"
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fm.choropleth_map('./example.json', 'FIPS', 'HR80', classification = 'Equal Interval', classes=6, tiles='Stamen Toner', fill_color='PuBuGn',save=True) "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Folium supports up to 6 classes."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Standardization"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/ljw/.local/lib/python3.5/site-packages/numpy/lib/function_base.py:3142: RuntimeWarning: Invalid value encountered in median\n",
+      "  RuntimeWarning)\n",
+      "/home/ljw/.local/lib/python3.5/site-packages/folium/folium.py:504: UserWarning: This method is deprecated. Please use Map.choropleth instead.\n",
+      "  warnings.warn('This method is deprecated. '\n"
      ]
     },
     {
-     "cell_type": "markdown",
+     "data": {
+      "text/html": [
+       "<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"data:text/html;base64,CiAgICAgICAgPCFET0NUWVBFIGh0bWw+CiAgICAgICAgPGhlYWQ+CiAgICAgICAgICAgIAogICAgICAgIAogICAgICAgICAgICA8bWV0YSBodHRwLWVxdWl2PSJjb250ZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD1VVEYtOCIgLz4KICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgCiAgICAgICAgICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9sZWFmbGV0LzAuNy4zL2xlYWZsZXQu [...]
+      ],
+      "text/plain": [
+       "<folium.folium.Map at 0x7fdb2b922470>"
+      ]
+     },
+     "execution_count": 19,
      "metadata": {},
-     "source": [
-      "Due to some pretty strong limitations in `Folium` itself, its usefulness for rich, dynamic, and interactive visualizations built from small, atomic functions is probably limited. It would need to be extended quite significantly (or linked with even more packages with more fickle APIs) to usefully generate mixed-mode scientific visualizations. With `Folium`'s natural linkages to `Pandas`, mapping derived quantites is also possible.\n",
-      "\n",
-      "But, it's really simple to map quickly from PySAL:"
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fm.choropleth_map('./example.json', 'FIPS', 'HR90', classification = 'Quantiles', std='HR80' , tiles='Stamen Toner', fill_color='PuBuGn',save=True) "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The goal of the `std` argument was to implement a very flexible transformation argument that could take functions, fields, and static items. As of now, it only supports float/int scaling and division by another field, which would support the mapping of spatial rates or population rates."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Conclusion"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Overall, the function neatly encapsulates three components:\n",
+    "\n",
+    "- plumbing large lists of arguments together to simplify mapping from JSON files\n",
+    "- searching for sane defaults (bounding box & zoom level)\n",
+    "- implementing classification and standardization useful for choropleth mapping"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Due to some pretty strong limitations in `Folium` itself, its usefulness for rich, dynamic, and interactive visualizations built from small, atomic functions is probably limited. It would need to be extended quite significantly (or linked with even more packages with more fickle APIs) to usefully generate mixed-mode scientific visualizations. With `Folium`'s natural linkages to `Pandas`, mapping derived quantites is also possible.\n",
+    "\n",
+    "But, it's really simple to map quickly from PySAL:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/ljw/.local/lib/python3.5/site-packages/folium/folium.py:504: UserWarning: This method is deprecated. Please use Map.choropleth instead.\n",
+      "  warnings.warn('This method is deprecated. '\n"
      ]
     },
     {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "with open('example2.json', 'w') as out:\n",
-      "    gj.dump(fm.build_features(ps.open(filepath + '.shp'), ps.open(filepath + '.dbf')), out)\n",
-      "fm.choropleth_map('example2.json', 'FIPS', 'HR70')"
-     ],
-     "language": "python",
+     "data": {
+      "text/html": [
+       "<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"data:text/html;base64,CiAgICAgICAgPCFET0NUWVBFIGh0bWw+CiAgICAgICAgPGhlYWQ+CiAgICAgICAgICAgIAogICAgICAgIAogICAgICAgICAgICA8bWV0YSBodHRwLWVxdWl2PSJjb250ZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD1VVEYtOCIgLz4KICAgICAgICAKICAgICAgICAgICAgCiAgICAgICAgCiAgICAgICAgICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9sZWFmbGV0LzAuNy4zL2xlYWZsZXQu [...]
+      ],
+      "text/plain": [
+       "<folium.folium.Map at 0x7fdb3433d1d0>"
+      ]
+     },
+     "execution_count": 20,
      "metadata": {},
-     "outputs": [
-      {
-       "html": [
-        "<iframe srcdoc=\"<!DOCTYPE html>\n",
-        "<head>\n",
-        "   <meta http-equiv="content-type" content="text/html; charset=UTF-8" />\n",
-        "   <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />\n",
-        "   <script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>\n",
-        "   <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>\n",
-        "   <script src="http://d3js.org/queue.v1.min.js"></script>\n",
-        "\n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "   \n",
-        "\n",
-        "   <style>\n",
-        "\n",
-        "      html, body {\n",
-        "        width: 100%;\n",
-        "        height: 100%;\n",
-        "        margin: 0;\n",
-        "        padding: 0;\n",
-        "      }\n",
-        "\n",
-        "      .legend {\n",
-        "          padding: 0px 0px;\n",
-        "          font: 10px sans-serif;\n",
-        "          background: white;\n",
-        "          background: rgba(255,255,255,0.8);\n",
-        "          box-shadow: 0 0 15px rgba(0,0,0,0.2);\n",
-        "          border-radius: 5px;\n",
-        "      }\n",
-        "\n",
-        "      .key path {\n",
-        "        display: none;\n",
-        "      }\n",
-        "\n",
-        "   </style>\n",
-        "</head>\n",
-        "\n",
-        "<body>\n",
-        "\n",
-        "   <div id="map" style="width: 960px; height: 500px"></div>\n",
-        "\n",
-        "   <script>\n",
-        "\n",
-        "      queue()\n",
-        "          .defer(d3.json, 'data.json')\n",
-        "          .defer(d3.json, 'example2.json')\n",
-        "          .await(makeMap)\n",
-        "\n",
-        "      function makeMap(error, data_1,gjson_1) {\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          function matchKey(datapoint, key_variable){\n",
-        "              if (typeof key_variable[0][datapoint] === 'undefined') {\n",
-        "                  return null;\n",
-        "              }\n",
-        "              else {\n",
-        "                  return parseFloat(key_variable[0][datapoint]);\n",
-        "              };\n",
-        "          };\n",
-        "\n",
-        "          \n",
-        "          var color = d3.scale.threshold()\n",
-        "              .domain([4.13585524408, 7.479368277879999, 10.925812736200001, 15.4596870396])\n",
-        "              .range(['#FFFFCC', '#D9F0A3', '#ADDD8E', '#78C679', '#41AB5D']);\n",
-        "          \n",
-        "\n",
-        "          var map = L.map('map').setView([32.7965526581, -90.8477554321], 5);\n",
-        "\n",
-        "          L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {\n",
-        "              maxZoom: 18,\n",
-        "              attribution: 'Map data (c) <a href="http://openstreetmap.org">OpenStreetMap</a> contributors'\n",
-        "          }).addTo(map);\n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          function style_1(feature) {\n",
-        "    return {\n",
-        "        fillColor: color(matchKey(feature.properties.FIPS, data_1)),\n",
-        "        weight: 1,\n",
-        "        opacity: 0.2,\n",
-        "        color: 'black',\n",
-        "        fillOpacity: 0.5\n",
-        "    };\n",
-        "}\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "          gJson_layer_1 = L.geoJson(gjson_1, {style: style_1}).addTo(map)\n",
-        "          \n",
-        "\n",
-        "          \n",
-        "              var legend = L.control({position: 'topright'});\n",
-        "\n",
-        "    legend.onAdd = function (map) {var div = L.DomUtil.create('div', 'legend'); return div};\n",
-        "\n",
-        "    legend.addTo(map);\n",
-        "\n",
-        "    var x = d3.scale.linear()\n",
-        "    .domain([0, 17])\n",
-        "    .range([0, 400]);\n",
-        "\n",
-        "    var xAxis = d3.svg.axis()\n",
-        "        .scale(x)\n",
-        "        .orient("top")\n",
-        "        .tickSize(1)\n",
-        "        .tickValues([4.13585524408, 7.479368277879999, 10.925812736200001, 15.4596870396]);\n",
-        "\n",
-        "    var svg = d3.select(".legend.leaflet-control").append("svg")\n",
-        "        .attr("id", 'legend')\n",
-        "        .attr("width", 450)\n",
-        "        .attr("height", 40);\n",
-        "\n",
-        "    var g = svg.append("g")\n",
-        "        .attr("class", "key")\n",
-        "        .attr("transform", "translate(25,16)");\n",
-        "\n",
-        "    g.selectAll("rect")\n",
-        "        .data(color.range().map(function(d, i) {\n",
-        "          return {\n",
-        "            x0: i ? x(color.domain()[i - 1]) : x.range()[0],\n",
-        "            x1: i < color.domain().length ? x(color.domain()[i]) : x.range()[1],\n",
-        "            z: d\n",
-        "          };\n",
-        "        }))\n",
-        "      .enter().append("rect")\n",
-        "        .attr("height", 10)\n",
-        "        .attr("x", function(d) { return d.x0; })\n",
-        "        .attr("width", function(d) { return d.x1 - d.x0; })\n",
-        "        .style("fill", function(d) { return d.z; });\n",
-        "\n",
-        "    g.call(xAxis).append("text")\n",
-        "        .attr("class", "caption")\n",
-        "        .attr("y", 21)\n",
-        "        .text('HR70');\n",
-        "          \n",
-        "\n",
-        "      };\n",
-        "\n",
-        "   </script>\n",
-        "</body>\" style=\"width: 100%; height: 510px; border: none\"></iframe>"
-       ],
-       "metadata": {},
-       "output_type": "pyout",
-       "prompt_number": 22,
-       "text": [
-        "<IPython.core.display.HTML at 0x7f53fd177810>"
-       ]
-      }
-     ],
-     "prompt_number": 22
+     "output_type": "execute_result"
     }
    ],
-   "metadata": {}
+   "source": [
+    "with open('example2.json', 'w') as out:\n",
+    "    gj.dump(fm.build_features(ps.open(filepath + '.shp'), ps.open(filepath + '.dbf')), out)\n",
+    "fm.choropleth_map('example2.json', 'FIPS', 'HR70')"
+   ]
   }
- ]
-}
\ No newline at end of file
+ ],
+ "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.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/pysal/contrib/viz/folium_mapping.py b/pysal/contrib/viz/folium_mapping.py
index e824355..1fef65a 100644
--- a/pysal/contrib/viz/folium_mapping.py
+++ b/pysal/contrib/viz/folium_mapping.py
@@ -6,16 +6,6 @@ import geojson as gj
 import os as os
 from IPython.display import HTML
 
-def inline_map(Map):
-    '''
-    Embeds the HTML source of the map directly into the IPython notebook.
-    
-    This method will not work if the map depends on any files (json data). Also this uses
-    the HTML5 srcdoc attribute, which may not be supported in all browsers.
-    '''
-    Map._build_map()
-    return HTML('<iframe srcdoc="{srcdoc}" style="width: 100%; height: 510px; border: none"></iframe>'.format(srcdoc=Map.HTML.replace('"', '"')))
-
 def build_features(shp, dbf):
     '''
     Builds a GeoJSON object from a PySAL shapefile and DBF object
@@ -266,7 +256,7 @@ def choropleth_map(jsonpath, key, attribute, df = None,
 
     if save:
         fname = jsonpath.rstrip('.json') + '_' + attribute + '.html'
-        choromap.create_map(fname)
+        choromap.save(fname)
     
-    return inline_map(choromap)
+    return choromap
 
diff --git a/pysal/core/util/wkb.py b/pysal/core/util/wkb.py
new file mode 100644
index 0000000..f0010e4
--- /dev/null
+++ b/pysal/core/util/wkb.py
@@ -0,0 +1,230 @@
+"""
+Load WKB into pysal shapes.
+
+Where pysal shapes support multiple parts, 
+"MULTI"type shapes will be converted to a single multi-part shape:
+    MULTIPOLYGON -> Polygon
+    MULTILINESTRING -> Chain
+
+Otherwise a list of shapes will be returned:
+    MULTIPOINT -> [pt0, ..., ptN]
+
+Some concepts aren't well supported by pysal shapes.
+For example:
+    wkt = 'MULTIPOLYGON EMPTY' -> '\x01   \x06\x00\x00\x00   \x00\x00\x00\x00'
+                                  |  <  | WKBMultiPolygon |    0 parts      |
+    pysal.cg.Polygon does not support 0 part polygons.
+    None is returned in this case.
+
+REFERENCE MATERIAL:
+SOURCE: http://webhelp.esri.com/arcgisserver/9.3/dotNet/index.htm#geodatabases/the_ogc_103951442.htm
+
+ Basic Type definitions
+ byte : 1 byte
+ uint32 : 32 bit unsigned integer  (4 bytes)
+ double : double precision number (8 bytes)
+
+ Building Blocks : Point, LinearRing
+
+
+"""
+from cStringIO import StringIO
+from pysal import cg
+import sys
+import array
+import struct
+
+__author__ = 'Charles R Schmidt <schmidtc at gmail.com>'
+__all__ = ['loads']
+
+"""
+enum wkbByteOrder {
+    wkbXDR = 0,              Big Endian
+    wkbNDR = 1               Little Endian
+};
+"""
+DEFAULT_ENDIAN = '<' if sys.byteorder == 'little' else '>'
+ENDIAN = {'\x00': '>', '\x01': '<'}
+
+def load_ring_little(dat):
+    """
+    LinearRing   {
+        uint32  numPoints;
+        Point   points[numPoints];
+    }
+    """
+    npts = struct.unpack('<I', dat.read(4))[0]
+    xy = struct.unpack('<%dd'%(npts*2), dat.read(npts*2*8))
+    return [cg.Point(xy[i:i+2]) for i in xrange(0,npts*2,2)]
+    
+def load_ring_big(dat):
+    npts = struct.unpack('>I', dat.read(4))[0]
+    xy = struct.unpack('>%dd'%(npts*2), dat.read(npts*2*8))
+    return [cg.Point(xy[i:i+2]) for i in xrange(0,npts*2,2)]
+    
+
+def loads(s):
+    """
+    WKBGeometry  {
+        union {
+            WKBPoint                        point;
+            WKBLineString               linestring;
+            WKBPolygon                  polygon;
+            WKBGeometryCollection   collection;
+            WKBMultiPoint               mpoint;
+            WKBMultiLineString      mlinestring;
+            WKBMultiPolygon         mpolygon;
+        }
+    };
+    """
+    # To allow recursive calls, read only the bytes we need.
+    if hasattr(s, 'read'):
+        dat = s
+    else:
+        dat = StringIO(s)
+    endian = ENDIAN[dat.read(1)]
+    typ = struct.unpack('I', dat.read(4))[0]
+    if typ == 1:
+        """
+        WKBPoint {
+            byte                byteOrder;
+            uint32          wkbType;                 1
+            Point               point;
+        }
+        Point {
+            double x;
+            double y;
+        };
+        """
+        x,y = struct.unpack(endian+'dd', dat.read(16))
+        return cg.Point((x,y))
+    elif typ == 2:
+        """
+        WKBLineString {
+            byte                byteOrder;
+            uint32          wkbType;                     2
+            uint32          numPoints;
+            Point               points[numPoints];
+        }
+        """
+        n = struct.unpack(endian+'I', dat.read(4))[0]
+        xy = struct.unpack(endian+'%dd'%(n*2), dat.read(n*2*8))
+        return cg.Chain([cg.Point(xy[i:i+2]) for i in xrange(0,n*2,2)])
+    elif typ == 3:
+        """
+        WKBPolygon  {
+            byte                byteOrder;
+            uint32          wkbType;                     3
+            uint32          numRings;
+            LinearRing      rings[numRings];
+        }
+
+        WKBPolygon has exactly 1 outer ring and n holes.
+            multipart Polygons are NOT support by WKBPolygon.
+        """
+        nrings = struct.unpack(endian+'I', dat.read(4))[0]
+        load_ring = load_ring_little if endian == '<' else load_ring_big
+        rings = [load_ring(dat) for _ in xrange(nrings)]
+        return cg.Polygon(rings[0], rings[1:])
+    elif typ == 4:
+        """
+        WKBMultiPoint   {
+            byte                byteOrder;
+            uint32          wkbType;                     4
+            uint32          num_wkbPoints;
+            WKBPoint            WKBPoints[num_wkbPoints];
+        }
+        """
+        npts = struct.unpack(endian+'I', dat.read(4))[0]
+        return [loads(dat) for _ in xrange(npts)]
+    elif typ == 5:
+        """
+        WKBMultiLineString  {
+            byte                byteOrder;
+            uint32          wkbType;                     5
+            uint32          num_wkbLineStrings;
+            WKBLineString   WKBLineStrings[num_wkbLineStrings];
+        }
+        """
+        nparts = struct.unpack(endian+'I', dat.read(4))[0]
+        chains = [loads(dat) for _ in xrange(nparts)]
+        return cg.Chain(sum([c.parts for c in chains],[]))
+    elif typ == 6:
+        """
+        wkbMultiPolygon {               
+            byte                byteOrder;                              
+            uint32          wkbType;                     6
+            uint32          num_wkbPolygons;
+            WKBPolygon      wkbPolygons[num_wkbPolygons];
+        }
+
+        """
+        npolys = struct.unpack(endian+'I', dat.read(4))[0]
+        polys = [loads(dat) for _ in xrange(npolys)]
+        parts = sum([p.parts for p in polys], [])
+        holes = sum([p.holes for p in polys if p.holes[0]], [])
+        # MULTIPOLYGON EMPTY, isn't well supported by pysal shape types.
+        if not parts:
+            return None
+        return cg.Polygon(parts, holes)
+    elif typ == 7:
+        """
+        WKBGeometryCollection {
+            byte                byte_order;
+            uint32          wkbType;                     7
+            uint32          num_wkbGeometries;
+            WKBGeometry     wkbGeometries[num_wkbGeometries]
+        }
+        """
+        ngeoms = struct.unpack(endian+'I', dat.read(4))[0]
+        return [loads(dat) for _ in xrange(ngeoms)]
+        
+    raise TypeError('Type (%d) is unknown or unsupported.'%typ)
+
+
+
+if __name__ == '__main__':
+    # TODO: Refactor below into Unit Tests
+    wktExamples = ['POINT(6 10)',
+                   'LINESTRING(3 4,10 50,20 25)',
+                   'POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2))',
+                   'MULTIPOINT(3.5 5.6,4.8 10.5)',
+                   'MULTILINESTRING((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4))',
+                    # This MULTIPOLYGON is not valid, the 2nd shell instects the 1st.
+                   #'MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2)),((3 3,6 2,6 4,3 3)))',
+                   'MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2)),((5 3,6 2,6 4,5 3)))',
+                   'GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))',
+                   #'POINT ZM (1 1 5 60)',  <-- ZM is not supported by WKB ?
+                   #'POINT M (1 1 80)',     <-- M is not supported by WKB ?
+                   #'POINT EMPTY',          <-- NOT SUPPORT
+                   'MULTIPOLYGON EMPTY']
+    # shapely only used for testing.
+    try:
+        import shapely.wkt, shapely.geometry
+        from pysal.contrib.shapely_ext import to_wkb
+    except ImportError:
+        print "shapely is used to test this module."
+        raise
+    for example in wktExamples:
+        print example
+        shape0= shapely.wkt.loads(example)
+        shape1 = loads(shape0.to_wkb())
+        if example.startswith('MULTIPOINT'):
+            shape2 = shapely.geometry.asMultiPoint(shape1)
+        elif example.startswith('GEOMETRYCOLLECTION'):
+            shape2 = shapely.geometry.collection.GeometryCollection(map(shapely.geometry.asShape,shape1))
+        elif example == 'MULTIPOLYGON EMPTY':
+            #Skip Test
+            shape2 = None
+        else:
+            shape2 = shapely.geometry.asShape(shape1)
+
+
+        print shape1
+        if shape2:
+            assert shape0.equals(shape2)
+            print shape0.equals(shape2)
+        else:
+            print "Skip"
+
+        print ""
diff --git a/pysal/core/util/wkt.py b/pysal/core/util/wkt.py
index 0907629..c638c9d 100644
--- a/pysal/core/util/wkt.py
+++ b/pysal/core/util/wkt.py
@@ -3,14 +3,6 @@ import re
 
 __author__ = "Charles R Schmidt <schmidtc at gmail.com>"
 __all__ = ['WKTParser']
-#####################################################################
-## ToDo: Add Well-Known-Binary support...
-##       * WKB spec:
-##  http://webhelp.esri.com/arcgisserver/9.3/dotNet/index.htm#geodatabases/the_ogc_103951442.htm
-##
-##
-#####################################################################
-
 
 class WKTParser:
     """ Class to represent OGC WKT, supports reading and writing
diff --git a/pysal/esda/gamma.py b/pysal/esda/gamma.py
index 5a8c5f5..3eb7d2f 100644
--- a/pysal/esda/gamma.py
+++ b/pysal/esda/gamma.py
@@ -82,8 +82,8 @@ class Gamma:
     >>> g = pysal.Gamma(y,w)
     >>> g.g
     20.0
-    >>> g.g_z
-    3.1879280354548638
+    >>> round(g.g_z, 3)
+    3.188
     >>> g.p_sim_g
     0.0030000000000000001
     >>> g.min_g
@@ -96,8 +96,8 @@ class Gamma:
     >>> g1 = pysal.Gamma(y,w,operation='s')
     >>> g1.g
     8.0
-    >>> g1.g_z
-    -3.7057554345954791
+    >>> round(g1.g_z, 3)
+    -3.706
     >>> g1.p_sim_g
     0.001
     >>> g1.min_g
@@ -110,8 +110,8 @@ class Gamma:
     >>> g2 = pysal.Gamma(y,w,operation='a')
     >>> g2.g
     8.0
-    >>> g2.g_z
-    -3.7057554345954791
+    >>> round(g2.g_z, 3)
+    -3.706
     >>> g2.p_sim_g
     0.001
     >>> g2.min_g
@@ -124,8 +124,8 @@ class Gamma:
     >>> g3 = pysal.Gamma(y,w,standardize='y')
     >>> g3.g
     32.0
-    >>> g3.g_z
-    3.7057554345954791
+    >>> round(g3.g_z, 3)
+    3.706
     >>> g3.p_sim_g
     0.001
     >>> g3.min_g
@@ -142,8 +142,8 @@ class Gamma:
     >>> g4 = pysal.Gamma(y,w,operation=func)
     >>> g4.g
     20.0
-    >>> g4.g_z
-    3.1879280354548638
+    >>> round(g4.g_z, 3)
+    3.188
     >>> g4.p_sim_g
     0.0030000000000000001
 
diff --git a/pysal/esda/mapclassify.py b/pysal/esda/mapclassify.py
index bc91801..c4f71bc 100644
--- a/pysal/esda/mapclassify.py
+++ b/pysal/esda/mapclassify.py
@@ -20,7 +20,7 @@ import scipy as sp
 import copy
 import sys
 from scipy.cluster.vq import kmeans as KMEANS
-
+from warnings import warn as Warn
 
 def headTail_breaks(values, cuts):
     """
@@ -126,7 +126,7 @@ def binC(y, bins):
     >>>
     """
 
-    if np.rank(y) == 1:
+    if np.ndim(y) == 1:
         k = 1
         n = np.shape(y)[0]
     else:
@@ -135,12 +135,12 @@ def binC(y, bins):
     for i, bin in enumerate(bins):
         b[np.nonzero(y == bin)] = i
 
-    # check for non-binned items and print a warning if needed
+    # check for non-binned items and warn if needed
     vals = set(y.flatten())
     for val in vals:
         if val not in bins:
-            print 'warning: value not in bin: ', val
-            print 'bins: ', bins
+            Warn('value not in bin: {}'.format(val), UserWarning)
+            Warn('bins: {}'.format(bins), UserWarning)
 
     return b
 
@@ -191,7 +191,7 @@ def bin(y, bins):
            [0, 0, 2]])
     >>>
     """
-    if np.rank(y) == 1:
+    if np.ndim(y) == 1:
         k = 1
         n = np.shape(y)[0]
     else:
@@ -301,8 +301,9 @@ def natural_breaks(values, k=5):
     uv = np.unique(values)
     uvk = len(uv)
     if uvk < k:
-        print 'Warning: Not enough unique values in array to form k classes'
-        print "Warning: setting k to %d" % uvk
+        Warn('Warning: Not enough unique values in array to form k classes', 
+                UserWarning)
+        Warn('Warning: setting k to %d' % uvk, UserWarning)
         k = uvk
     kres = _kmeans(values, k)
     sids = kres[-1]  # centroids
@@ -382,7 +383,7 @@ def _fisher_jenks_means(values, classes=5, sort=True):
     return kclass
 
 
-class Map_Classifier:
+class Map_Classifier(object):
     """
     Abstract class for all map classifications [Slocum2008]_
 
@@ -448,6 +449,50 @@ class Map_Classifier:
     def _classify(self):
         self._set_bins()
         self.yb, self.counts = bin1d(self.y, self.bins)
+    
+    def _update(self, data, *args, **kwargs):
+        """
+        The only thing that *should* happen in this function is 
+        1. input sanitization for pandas
+        2. classification/reclassification. 
+        
+        Using their __init__ methods, all classifiers can re-classify given
+        different input parameters or additional data. 
+
+        If you've got a cleverer updating equation than the intial estimation
+        equation, remove the call to self.__init__ below and replace it with the
+        updating function. 
+        """
+        if data is not None:
+            if hasattr(data, 'values'):
+                data = data.values
+            data = np.append(data.flatten(), self.y)
+        else:
+            data = self.y
+        self.__init__(data, *args, **kwargs)
+    
+    def update(self, y=None, inplace=False, **kwargs):
+        """
+        Add data or change classification parameters. 
+
+        Parameters
+        ----------
+        y   :   array
+                    (n,1) array of data to classify
+        inplace :   bool
+                    whether to conduct the update in place or to return a copy
+                    estimated from the additional specifications. 
+
+        Additional parameters provided in **kwargs are passed to the init
+        function of the class. For documentation, check the class constructor.
+        """
+        kwargs.update({'k':kwargs.pop('k', self.k)})
+        if inplace:
+            self._update(y, **kwargs)
+        else:
+            new = copy.deepcopy(self)
+            new._update(y, **kwargs)
+            return new
 
     def __str__(self):
         st = self._table_string()
@@ -456,6 +501,19 @@ class Map_Classifier:
     def __repr__(self):
         return self._table_string()
 
+    def __call__(self, *args, **kwargs):
+        """
+        This will allow the classifier to be called like it's a function.
+
+        Whether or not we want to make this be "find_bin" or "update" is a
+        design decision. 
+
+        I like this as find_bin, since a classifier's job should be to classify
+        the data given to it using the rules estimated from the `_classify()`
+        function. 
+        """
+        return self.find_bin(*args)
+
     def get_tss(self):
         """
         Total sum of squares around class means
@@ -543,7 +601,30 @@ class Map_Classifier:
         table.insert(1, " ")
         table = "\n".join(table)
         return table
+    
+    def find_bin(self, x):
+        """
+        Sort input or inputs according to the current bin estimate
 
+        Parameters
+        ----------
+        x       :   array or numeric
+                    a value or array of values to fit within the estimated
+                    bins
+
+        Returns
+        -------
+        a bin index or array of bin indices that classify the input into one of
+        the classifiers' bins
+        """
+        if not isinstance(x, np.ndarray):
+            x = np.array([x]).flatten()
+        uptos = [np.where(value < self.bins)[0] for value in x]
+        bins = [x.min() if x.size > 0 else len(self.bins)-1 for x in uptos] #bail upwards
+        if len(bins) == 1:
+            return bins[0]
+        else:
+            return bins
 
 class HeadTail_Breaks(Map_Classifier):
     """
@@ -738,6 +819,28 @@ class Percentiles(Map_Classifier):
         self.bins = np.array([stats.scoreatpercentile(y, p) for p in pct])
         self.k = len(self.bins)
 
+    def update(self, y=None, inplace=False, **kwargs):
+        """
+        Add data or change classification parameters. 
+
+        Parameters
+        ----------
+        y   :   array
+                    (n,1) array of data to classify
+        inplace :   bool
+                    whether to conduct the update in place or to return a copy
+                    estimated from the additional specifications. 
+
+        Additional parameters provided in **kwargs are passed to the init
+        function of the class. For documentation, check the class constructor.
+        """
+        kwargs.update({'pct':kwargs.pkp('pct', self.pct)})
+        if inplace:
+            self._update(y, **kwargs)
+        else:
+            new = copy.deepcopy(self)
+            new._update(y, **kwargs)
+            return new
 
 class Box_Plot(Map_Classifier):
     """
@@ -839,7 +942,29 @@ class Box_Plot(Map_Classifier):
         Map_Classifier._classify(self)
         self.low_outlier_ids = np.nonzero(self.yb == 0)[0]
         self.high_outlier_ids = np.nonzero(self.yb == 5)[0]
+    
+    def update(self, y=None,  inplace=False, **kwargs):
+        """
+        Add data or change classification parameters. 
 
+        Parameters
+        ----------
+        y       :   array
+                        (n,1) array of data to classify
+        inplace     :   bool
+                        whether to conduct the update in place or to return a copy
+                        estimated from the additional specifications. 
+
+        Additional parameters provided in **kwargs are passed to the init
+        function of the class. For documentation, check the class constructor.
+        """
+        kwargs.update({'hinge':kwargs.pop('hinge', self.hinge)})
+        if inplace:
+            self._update(y, **kwargs)
+        else:
+            new = copy.deepcopy(self)
+            new._update(y, **kwargs)
+            return new
 
 class Quantiles(Map_Classifier):
     """
@@ -951,6 +1076,30 @@ class Std_Mean(Map_Classifier):
             cuts.append(y_max)
         self.bins = np.array(cuts)
         self.k = len(cuts)
+    
+    def update(self, y=None, inplace=False, **kwargs):
+        """
+        Add data or change classification parameters. 
+
+        Parameters
+        ----------
+        y   :   array
+                    (n,1) array of data to classify
+        inplace :   bool
+                    whether to conduct the update in place or to return a copy
+                    estimated from the additional specifications. 
+
+        Additional parameters provided in **kwargs are passed to the init
+        function of the class. For documentation, check the class constructor.
+        """
+        kwargs.update({'multiples':kwargs.pop('multiples', self.multiples)})
+        if inplace:
+            self._update(y, **kwargs)
+        else:
+            new = copy.deepcopy(self)
+            new._update(y, **kwargs)
+            return new
+
 
 
 class Maximum_Breaks(Map_Classifier):
@@ -1025,6 +1174,29 @@ class Maximum_Breaks(Map_Classifier):
         mp.sort()
         self.bins = np.array(mp)
 
+    def update(self, y=None, inplace=False, **kwargs):
+        """
+        Add data or change classification parameters. 
+
+        Parameters
+        ----------
+        y   :   array
+                    (n,1) array of data to classify
+        inplace :   bool
+                    whether to conduct the update in place or to return a copy
+                    estimated from the additional specifications. 
+
+        Additional parameters provided in **kwargs are passed to the init
+        function of the class. For documentation, check the class constructor.
+        """
+        kwargs.update({'k':kwargs.pop('k', self.k)})
+        kwargs.update({'mindiff':kwargs.pop('mindiff', self.mindiff)})
+        if inplace:
+            self._update(y, **kwargs)
+        else:
+            new = copy.deepcopy(self)
+            new._update(y, **kwargs)
+            return new
 
 class Natural_Breaks(Map_Classifier):
     """
@@ -1099,8 +1271,9 @@ class Natural_Breaks(Map_Classifier):
         uv = np.unique(values)
         uvk = len(uv)
         if uvk < k:
-            print 'Warning: Not enough unique values in array to form k classes'
-            print "Warning: setting k to %d" % uvk
+            Warn('Warning: Not enough unique values in array to form k classes',
+                    UserWarning)
+            Warn("Warning: setting k to %d" % uvk, UserWarning)
             k = uvk
             uv.sort()
             # we set the bins equal to the sorted unique values and ramp k
@@ -1119,6 +1292,29 @@ class Natural_Breaks(Map_Classifier):
             self.bins = np.array(res0[-1])
             self.k = len(self.bins)
 
+    def update(self, y=None, inplace=False, **kwargs):
+        """
+        Add data or change classification parameters. 
+
+        Parameters
+        ----------
+        y           :   array
+                        (n,1) array of data to classify
+        inplace     :   bool
+                        whether to conduct the update in place or to return a copy
+                        estimated from the additional specifications. 
+
+        Additional parameters provided in **kwargs are passed to the init
+        function of the class. For documentation, check the class constructor.
+        """
+        kwargs.update({'k':kwargs.pop('k', self.k)})
+        kwargs.update({'initial':kwargs.pop('initial', self.initial)})
+        if inplace:
+            self._update(y, **kwargs)
+        else:
+            new = copy.deepcopy(self)
+            new._update(y, **kwargs)
+            return new
 
 class Fisher_Jenks(Map_Classifier):
     """
@@ -1220,6 +1416,7 @@ class Fisher_Jenks_Sampled(Map_Classifier):
         yr[0] = min(y)  # make sure we have the min
         self.original_y = y
         self.pct = pct
+        self._truncated = truncate
         self.yr = yr
         self.yr_n = yr.size
         Map_Classifier.__init__(self, yr)
@@ -1233,6 +1430,31 @@ class Fisher_Jenks_Sampled(Map_Classifier):
         self.bins = fj.bins
 
 
+    def update(self, y=None, inplace=False, **kwargs):
+        """
+        Add data or change classification parameters. 
+
+        Parameters
+        ----------
+        y           :   array
+                        (n,1) array of data to classify
+        inplace     :   bool
+                        whether to conduct the update in place or to return a copy
+                        estimated from the additional specifications. 
+
+        Additional parameters provided in **kwargs are passed to the init
+        function of the class. For documentation, check the class constructor.
+        """
+        kwargs.update({'k':kwargs.pop('k', self.k)})
+        kwargs.update({'pct':kwargs.pop('pct', self.pct)})
+        kwargs.update({'truncate':kwargs.pop('truncate', self._truncated)})
+        if inplace:
+            self._update(y, **kwargs)
+        else:
+            new = copy.deepcopy(self)
+            new._update(y, **kwargs)
+            return new
+
 class Jenks_Caspall(Map_Classifier):
     """
     Jenks Caspall  Map Classification
@@ -1303,8 +1525,7 @@ class Jenks_Caspall(Map_Classifier):
         cuts.shape = (len(cuts),)
         self.bins = cuts
         self.iterations = it
-
-
+  
 class Jenks_Caspall_Sampled(Map_Classifier):
     """
     Jenks Caspall Map Classification using a random sample
@@ -1389,7 +1610,30 @@ class Jenks_Caspall_Sampled(Map_Classifier):
         jc = Jenks_Caspall(self.y, self.k)
         self.bins = jc.bins
         self.iterations = jc.iterations
+    
+    def update(self, y=None, inplace=False, **kwargs):
+        """
+        Add data or change classification parameters. 
 
+        Parameters
+        ----------
+        y           :   array
+                        (n,1) array of data to classify
+        inplace     :   bool
+                        whether to conduct the update in place or to return a copy
+                        estimated from the additional specifications. 
+
+        Additional parameters provided in **kwargs are passed to the init
+        function of the class. For documentation, check the class constructor.
+        """
+        kwargs.update({'k':kwargs.pop('k', self.k)})
+        kwargs.update({'pct':kwargs.pop('pct', self.pct)})
+        if inplace:
+            self._update(y, **kwargs)
+        else:
+            new = copy.deepcopy(self)
+            new._update(y, **kwargs)
+            return new
 
 class Jenks_Caspall_Forced(Map_Classifier):
     """
@@ -1599,6 +1843,39 @@ class User_Defined(Map_Classifier):
     def _set_bins(self):
         pass
 
+    def _update(self, y=None, bins=None):
+        if y is not None:
+            if hasattr(y, 'values'):
+                y = y.values
+            y = np.append(y.flatten(), self.y)
+        else:
+            y = self.y
+        if bins is None:
+            bins = self.bins
+        self.__init__(y, bins)
+
+    def update(self, y=None, inplace=False, **kwargs):
+        """
+        Add data or change classification parameters. 
+
+        Parameters
+        ----------
+        y           :   array
+                        (n,1) array of data to classify
+        inplace     :   bool
+                        whether to conduct the update in place or to return a copy
+                        estimated from the additional specifications. 
+
+        Additional parameters provided in **kwargs are passed to the init
+        function of the class. For documentation, check the class constructor.
+        """
+        bins = kwargs.pop('bins', self.bins)
+        if inplace:
+            self._update(y=y, bins=bins, **kwargs)
+        else:
+            new = copy.deepcopy(self)
+            new._update(y, bins, **kwargs)
+            return new
 
 class Max_P_Classifier(Map_Classifier):
     """
@@ -1766,6 +2043,28 @@ class Max_P_Classifier(Map_Classifier):
         else:
             return True
 
+    def update(self, y=None, inplace=False, **kwargs):
+        """
+        Add data or change classification parameters. 
+
+        Parameters
+        ----------
+        y           :   array
+                        (n,1) array of data to classify
+        inplace     :   bool
+                        whether to conduct the update in place or to return a copy
+                        estimated from the additional specifications. 
+
+        Additional parameters provided in **kwargs are passed to the init
+        function of the class. For documentation, check the class constructor.
+        """
+        kwargs.update({'initial':kwargs.pop('initial', self.initial)})
+        if inplace:
+            self._update(y, bins, **kwargs)
+        else:
+            new = copy.deepcopy(self)
+            new._update(y, bins, **kwargs)
+            return new
 
 def _fit(y, classes):
     """Calculate the total sum of squares for a vector y classified into
@@ -1867,7 +2166,7 @@ def gadf(y, method="Quantiles", maxk=15, pct=0.8):
     return (k, cl, gadf)
 
 
-class K_classifiers:
+class K_classifiers(object):
     """
     Evaluate all k-classifers and pick optimal based on k and GADF
 
diff --git a/pysal/esda/moran.py b/pysal/esda/moran.py
index c04862f..b3ec42a 100644
--- a/pysal/esda/moran.py
+++ b/pysal/esda/moran.py
@@ -553,17 +553,17 @@ class Moran_Local:
     permutations : int
                    number of random permutations for calculation of pseudo
                    p_values
-    Is           : float
-                   value of Moran's I
+    Is           : array
+                   local Moran's I values
     q            : array
                    (if permutations>0)
-                   values indicate quadrat location 1 HH,  2 LH,  3 LL,  4 HL
-    sim          : array
+                   values indicate quandrant location 1 HH,  2 LH,  3 LL,  4 HL
+    sim          : array (permutations by n)
                    (if permutations>0)
-                   vector of I values for permuted samples
+                   I values for permuted samples
     p_sim        : array
                    (if permutations>0)
-                   p-value based on permutations (one-sided)
+                   p-values based on permutations (one-sided)
                    null: spatial randomness
                    alternative: the observed Ii is further away or extreme
                    from the median of simulated values. It is either extremelyi
@@ -739,7 +739,7 @@ class Moran_Local_BV:
                    value of Moran's I
     q            : array
                    (if permutations>0)
-                   values indicate quadrat location 1 HH,  2 LH,  3 LL,  4 HL
+                   values indicate quandrant location 1 HH,  2 LH,  3 LL,  4 HL
     sim          : array
                    (if permutations>0)
                    vector of I values for permuted samples
@@ -929,7 +929,7 @@ class Moran_Local_Rate(Moran_Local):
                    value of Moran's I
     q            : array
                    (if permutations>0)
-                   values indicate quadrat location 1 HH,  2 LH,  3 LL,  4 HL
+                   values indicate quandrant location 1 HH,  2 LH,  3 LL,  4 HL
     sim          : array
                    (if permutations>0)
                    vector of I values for permuted samples
diff --git a/pysal/esda/tests/test_mapclassify.py b/pysal/esda/tests/test_mapclassify.py
index d8ebc7e..a8f3428 100644
--- a/pysal/esda/tests/test_mapclassify.py
+++ b/pysal/esda/tests/test_mapclassify.py
@@ -1,10 +1,10 @@
 import pysal
 from pysal.esda.mapclassify import *
 from pysal.esda.mapclassify import binC, bin, bin1d
+from pysal.common import RTOL
 import numpy as np
 import unittest
 
-
 class TestQuantile(unittest.TestCase):
     def test_quantile(self):
         y = np.arange(1000)
@@ -23,6 +23,49 @@ class TestQuantile(unittest.TestCase):
             np.testing.assert_almost_equal(k, len(quantile(y, k)))
             self.assertEqual(k, len(quantile(y, k)))
 
+class TestUpdate(unittest.TestCase):
+    def setUp(self):
+        np.random.seed(4414)
+        self.data = np.random.normal(0,10, size=10)
+        self.new_data = np.random.normal(0,10,size=4)
+    def test_update(self):
+        #Quantiles
+        quants = Quantiles(self.data, k=3)
+        known_yb = np.array([0,1,0,1,0,2,0,2,1,2])
+        np.testing.assert_allclose(quants.yb, known_yb, rtol=RTOL)
+        new_yb = quants.update(self.new_data, k=4).yb
+        known_new_yb = np.array([0,3,1,0,1,2,0,2,1,3,0,3,2,3])
+        np.testing.assert_allclose(new_yb, known_new_yb, rtol=RTOL)
+
+        #User-Defined
+        ud = User_Defined(self.data, [-20,0,5,20])
+        known_yb = np.array([1,2,1,1,1,2,0,2,1,3]) 
+        np.testing.assert_allclose(ud.yb, known_yb, rtol=RTOL)
+        new_yb = ud.update(self.new_data).yb
+        known_new_yb = np.array([1,3,1,1,1,2,1,1,1,2,0,2,1,3])
+        np.testing.assert_allclose(new_yb, known_new_yb, rtol=RTOL)
+
+        #Fisher-Jenks Sampled
+        fjs = Fisher_Jenks_Sampled(self.data, k=3, pct=70)
+        known_yb = np.array([1,2,0,1,1,2,0,2,1,2])
+        np.testing.assert_allclose(known_yb, fjs.yb, rtol=RTOL)
+        new_yb = fjs.update(self.new_data, k=2).yb
+        known_new_yb = np.array([0,1,0,0,0,1,0,0,0,1,0,1,0,1])
+        np.testing.assert_allclose(known_new_yb, new_yb, rtol=RTOL)
+
+class TestFindBin(unittest.TestCase):
+    def setUp(self):
+        dat = pysal.open(pysal.examples.get_path("calempdensity.csv"))
+        self.V = np.array([record[-1] for record in dat])
+    
+    def test_find_bin(self):
+        toclass = [0,1,3,5,50,70,101,202,390,505,800,5000,5001]
+        mc = Fisher_Jenks(self.V, k=5)
+        known = [0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 4, 4, 4]
+        np.testing.assert_array_equal(known, mc.find_bin(toclass))
+        mc2 = Fisher_Jenks(self.V, k=9)
+        known = [0, 0, 0, 0, 2, 2, 3, 5, 7, 7, 8, 8, 8]
+        np.testing.assert_array_equal(known, mc2.find_bin(toclass))
 
 class TestBinC(unittest.TestCase):
     def test_bin_c(self):
@@ -172,7 +215,6 @@ class TestMapClassifier(unittest.TestCase):
         # self.assertEqual(expected, map__classifier.get_tss())
         assert True  # TODO: implement your test here
 
-
 class TestEqualInterval(unittest.TestCase):
     def setUp(self):
         dat = pysal.open(pysal.examples.get_path("calempdensity.csv"))
@@ -228,7 +270,6 @@ class TestQuantiles(unittest.TestCase):
         np.testing.assert_array_almost_equal(q.counts,
                                              np.array([12, 11, 12, 11, 12]))
 
-
 class TestStdMean(unittest.TestCase):
     def setUp(self):
         dat = pysal.open(pysal.examples.get_path("calempdensity.csv"))
diff --git a/pysal/spatial_dynamics/interaction.py b/pysal/spatial_dynamics/interaction.py
index 2f2933e..2f3f396 100644
--- a/pysal/spatial_dynamics/interaction.py
+++ b/pysal/spatial_dynamics/interaction.py
@@ -47,10 +47,10 @@ class SpaceTimeEvents:
     t               : array
                       (n, 1), array of the temporal coordinates for the events.
     space           : array
-                      (n, 1), array of the spatial coordinates (x,y) for the
+                      (n, 2), array of the spatial coordinates (x,y) for the
                       events.
     time            : array
-                      (n, 1), array of the temporal coordinates (t,1) for the
+                      (n, 2), array of the temporal coordinates (t,1) for the
                       events, the second column is a vector of ones.
 
     Examples
diff --git a/pysal/spreg/tests/test_ml_error.py b/pysal/spreg/tests/test_ml_error.py
index bebdde1..a94f454 100644
--- a/pysal/spreg/tests/test_ml_error.py
+++ b/pysal/spreg/tests/test_ml_error.py
@@ -61,7 +61,7 @@ class TestMLError(unittest.TestCase):
  (-4.8232686291115678, 1.4122456582517099e-06),
  (3.9913060809142995, 6.5710406838016854e-05),
  (7.9088780724028922, 2.5971882547279339e-15)]
-        np.testing.assert_allclose(reg.z_stat,z_stat,RTOL)
+        np.testing.assert_allclose(reg.z_stat,z_stat,0.0001)
         logll = -4471.407066887894
         np.testing.assert_allclose(reg.logll,logll,RTOL)
         aic = 8952.8141337757879
diff --git a/pysal/spreg/tests/test_sur_error.py b/pysal/spreg/tests/test_sur_error.py
index b0d56be..c1eefc4 100644
--- a/pysal/spreg/tests/test_sur_error.py
+++ b/pysal/spreg/tests/test_sur_error.py
@@ -7,6 +7,9 @@ from pysal.common import RTOL
 
 PEGP = pysal.examples.get_path
 
+ATOL = 0.00001
+
+
 def dict_compare(actual, desired, rtol):
     for i in actual.keys():
         np.testing.assert_allclose(actual[i],desired[i],rtol)
@@ -36,7 +39,7 @@ class Test_SUR_error(unittest.TestCase):
        [  4.267954e-02,   9.935169e+00,   2.926783e-23]]),\
          1: np.array([[  3.31399691e-01,   9.20106497e+00,   3.54419478e-20],\
         [  1.33525912e-01,   8.31094371e+00,   9.49439563e-17],\
-        [  4.00409716e-02,   1.17568780e+01,   6.50970965e-32]])},RTOL)
+        [  4.00409716e-02,   1.17568780e+01,   6.50970965e-32]])},0.0001)
         np.testing.assert_allclose(reg.lamols,np.array([[ 0.60205035],[ 0.56056348]]),RTOL)
         np.testing.assert_allclose(reg.lamsur,np.array([[ 0.54361986],[ 0.50445451]]),RTOL)
         np.testing.assert_allclose(reg.corr,np.array([[ 1.        ,  0.31763719],\
@@ -64,7 +67,7 @@ class Test_SUR_error(unittest.TestCase):
        [  4.267954e-02,   9.935169e+00,   2.926783e-23]]),\
          1: np.array([[  3.31399691e-01,   9.20106497e+00,   3.54419478e-20],\
         [  1.33525912e-01,   8.31094371e+00,   9.49439563e-17],\
-        [  4.00409716e-02,   1.17568780e+01,   6.50970965e-32]])},RTOL)
+        [  4.00409716e-02,   1.17568780e+01,   6.50970965e-32]])},0.0001)
         np.testing.assert_allclose(reg.vm, np.array([[  4.14625293e-04,   2.38494923e-05,  -3.48748935e-03,\
          -5.55994101e-04,  -1.63239040e-04],[  2.38494923e-05,   4.53642714e-04,  -2.00602452e-04,\
          -5.46893937e-04,  -3.10498019e-03],[ -3.48748935e-03,  -2.00602452e-04,   7.09989591e-01,\
@@ -72,22 +75,22 @@ class Test_SUR_error(unittest.TestCase):
           3.42890248e-01,   1.91931389e-01],[ -1.63239040e-04,  -3.10498019e-03,   6.39785285e-02,\
           1.91931389e-01,   5.86933821e-01]]),RTOL)
         np.testing.assert_allclose(reg.lamsetp,(np.array([[ 0.02036235],\
-        [ 0.02129889]]), np.array([[ 26.69730489],[ 23.68454458]]), np.array([[  5.059048e-157],\
-        [  5.202838e-124]])),RTOL)
-        np.testing.assert_allclose(reg.joinlam,(1207.81269, 2, 5.330924e-263))
+        [ 0.02129889]]), np.array([[ 26.69730489],[ 23.68454458]]), np.array([[  0.0],\
+        [  0.0]])),rtol=0.001,atol=ATOL)
+        np.testing.assert_allclose(reg.joinlam,(1207.81269, 2, 0.0),atol=0.0001,rtol=0.00001)
         np.testing.assert_allclose(reg.surchow,[(5.1073696860799931, 1, 0.023824413482255974),
         (1.9524745281321374, 1, 0.16232044613203933),
-        (0.79663667463065702, 1, 0.37210085476281407)],RTOL)
+        (0.79663667463065702, 1, 0.37210085476281407)],0.0001)
         np.testing.assert_allclose(reg.likrlambda,(1014.0319285186415, 2, 6.3938800607190098e-221))
-        np.testing.assert_allclose(reg.lrtest, (287.95821154104488, 1, 1.3849971230596533e-64))
-        np.testing.assert_allclose(reg.lamtest, (1.8693306894921564, 1, 0.17155175615429052))
+        np.testing.assert_allclose(reg.lrtest, (287.95821154104488, 1, 1.3849971230596533e-64),rtol=0.0001, atol=ATOL)
+        np.testing.assert_allclose(reg.lamtest, (1.8693306894921564, 1, 0.17155175615429052),rtol=0.0001, atol=ATOL)
 
     def test_error_3eq(self): #Three equation example, unequal K
         y_var1 = ['HR60','HR70','HR80']
         x_var1 = [['RD60','PS60'],['RD70','PS70','UE70'],['RD80','PS80']]
         bigy1,bigX1,bigyvars1,bigXvars1 = sur_dictxy(self.db,y_var1,x_var1)
         reg = SURerrorML(bigy1,bigX1,self.w,name_bigy=bigyvars1,name_bigX=bigXvars1,\
-            name_w="natqueen",name_ds="natregimes")        
+            name_w="natqueen",name_ds="natregimes")
 
         dict_compare(reg.bSUR0,{0: np.array([[ 4.50407527],[ 2.39199682],[ 0.52723694]]), 1: np.array([[ 7.44509818],\
         [ 3.74968571],[ 1.28811685],[-0.23526451]]), 2: np.array([[ 6.92761614],[ 3.65423052],\
@@ -104,7 +107,7 @@ class Test_SUR_error(unittest.TestCase):
        [  5.378335e-002,  -3.185738e+000,   1.443854e-003]]),\
          2: np.array([[  1.500528e-001,   4.608718e+001,   0.000000e+000],\
        [  1.236340e-001,   2.987457e+001,   4.210941e-196],\
-       [  1.194989e-001,   1.174869e+001,   7.172248e-032]])},RTOL)
+       [  1.194989e-001,   1.174869e+001,   7.172248e-032]])},0.001)
         np.testing.assert_allclose(reg.lamols,np.array([[ 0.4248829 ],[ 0.46428101],[ 0.42823999]]),RTOL)
         np.testing.assert_allclose(reg.lamsur,np.array([[ 0.36137603],[ 0.38321666],[ 0.37183716]]),RTOL)
         np.testing.assert_allclose(reg.corr,np.array([[ 1.,          0.24563253,  0.14986527],\
diff --git a/pysal/spreg/user_output.py b/pysal/spreg/user_output.py
index 69fa1d1..e9523f4 100644
--- a/pysal/spreg/user_output.py
+++ b/pysal/spreg/user_output.py
@@ -309,7 +309,7 @@ def set_name_multi(multireg, multi_set, name_multiID, y, x, name_y, name_x, name
 def check_arrays(*arrays):
     """Check if the objects passed by a user to a regression class are
     correctly structured. If the user's data is correctly formed this function
-    returns nothing, if not then an exception is raised. Note, this does not 
+    returns nothing, if not then an exception is raised. Note, this does not
     check for model setup, simply the shape and types of the objects.
 
     Parameters
@@ -366,7 +366,7 @@ def check_arrays(*arrays):
 def check_y(y, n):
     """Check if the y object passed by a user to a regression class is
     correctly structured. If the user's data is correctly formed this function
-    returns nothing, if not then an exception is raised. Note, this does not 
+    returns nothing, if not then an exception is raised. Note, this does not
     check for model setup, simply the shape and types of the objects.
 
     Parameters
@@ -466,7 +466,7 @@ def check_weights(w, y, w_required=False):
 
 def check_robust(robust, wk):
     """Check if the combination of robust and wk parameters passed by the user
-    are valid. Note: this does not check if the W object is a valid adaptive 
+    are valid. Note: this does not check if the W object is a valid adaptive
     kernel weights matrix needed for the HAC.
 
     Parameters
diff --git a/pysal/version.py b/pysal/version.py
index 11da3db..4dcf25d 100644
--- a/pysal/version.py
+++ b/pysal/version.py
@@ -1,3 +1,3 @@
 import datetime
-version = "1.11.0"
-stable_release_date = datetime.date(2016, 1, 27)
+version = "1.11.1"
+stable_release_date = datetime.date(2016, 4, 1)
diff --git a/pysal/weights/Contiguity.py b/pysal/weights/Contiguity.py
index 0db7ef9..c26a185 100644
--- a/pysal/weights/Contiguity.py
+++ b/pysal/weights/Contiguity.py
@@ -21,7 +21,7 @@ def buildContiguity(polygons, criterion="rook", ids=None):
     Parameters
     ----------
 
-    polygons   : 
+    polygons   :
                  an instance of a pysal geo file handler
                  Any thing returned by pysal.open that is explicitly polygons
     criterion  : string
@@ -32,7 +32,7 @@ def buildContiguity(polygons, criterion="rook", ids=None):
     Returns
     -------
 
-    w         : W 
+    w         : W
                 instance; Contiguity weights object
 
     Examples
@@ -56,9 +56,9 @@ def buildContiguity(polygons, criterion="rook", ids=None):
     >>> fips = pysal.open(pysal.examples.get_path('10740.dbf')).by_col('STFID')
     >>> w = buildContiguity(pysal.open(pysal.examples.get_path('10740.shp'),'r'),ids=fips)
     WARNING: there is one disconnected observation (no neighbors)
-    Island id:  ['35043940300']
+    Island id:  [u'35043940300']
     >>> w['35001000107']
-    {'35001003805': 1.0, '35001003721': 1.0, '35001000111': 1.0, '35001000112': 1.0, '35001000108': 1.0}
+    {u'35001003805': 1.0, u'35001003721': 1.0, u'35001000111': 1.0, u'35001000112': 1.0, u'35001000108': 1.0}
 
     Notes
     -----
diff --git a/pysal/weights/Distance.py b/pysal/weights/Distance.py
index 1727b88..7d6bec1 100644
--- a/pysal/weights/Distance.py
+++ b/pysal/weights/Distance.py
@@ -411,8 +411,13 @@ class DistanceBand(W):
     >>> w=DistanceBand(points,threshold=14.2)
     >>> w.weights
     {0: [1, 1], 1: [1, 1, 1], 2: [1], 3: [1, 1], 4: [1, 1, 1], 5: [1]}
-    >>> w.neighbors
-    {0: [1, 3], 1: [0, 3, 4], 2: [4], 3: [1, 0], 4: [5, 1, 2], 5: [4]}
+    >>> ps.weights.util.neighbor_equality(w,pysal.W( {0: [1, 3], 1: [0, 3, 4],
+                                                     2: [4], 3: [1, 0], 4:
+                                                     [5, 2, 1], 5: [4]}))
+    True
+
+
+
 
     inverse distance weights
 
@@ -485,22 +490,24 @@ class DistanceBand(W):
         if self.binary:
             for key,weight in self.dmat.items():
                 i,j = key
-                if j not in neighbors[i]:
-                    weights[i].append(1)
-                    neighbors[i].append(j)
-                if i not in neighbors[j]:
-                    weights[j].append(1)
-                    neighbors[j].append(i)
+                if i != j:
+                    if j not in neighbors[i]:
+                        weights[i].append(1)
+                        neighbors[i].append(j)
+                    if i not in neighbors[j]:
+                        weights[j].append(1)
+                        neighbors[j].append(i)
 
         else:
             for key,weight in self.dmat.items():
                 i,j = key
-                if j not in neighbors[i]:
-                    weights[i].append(weight**self.alpha)
-                    neighbors[i].append(j)
-                if i not in neighbors[j]:
-                    weights[j].append(weight**self.alpha)
-                    neighbors[j].append(i)
+                if i != j:
+                    if j not in neighbors[i]:
+                        weights[i].append(weight**self.alpha)
+                        neighbors[i].append(j)
+                    if i not in neighbors[j]:
+                        weights[j].append(weight**self.alpha)
+                        neighbors[j].append(i)
 
         return neighbors, weights
 
diff --git a/pysal/weights/spatial_lag.py b/pysal/weights/spatial_lag.py
index 2501670..5615ad7 100644
--- a/pysal/weights/spatial_lag.py
+++ b/pysal/weights/spatial_lag.py
@@ -1,9 +1,11 @@
 """
 Spatial lag operations.
 """
-__author__ = "Sergio J. Rey <srey at asu.edu>, David C. Folch <david.folch at asu.edu>"
-__all__ = ['lag_spatial']
+__author__ = "Sergio J. Rey <srey at asu.edu>, David C. Folch <david.folch at asu.edu>, Levi John Wolf <ljw2 at asu.edu"
+__all__ = ['lag_spatial', 'lag_categorical']
 
+import numpy as np
+from six import iteritems as diter
 
 def lag_spatial(w, y):
     """
@@ -82,3 +84,127 @@ def lag_spatial(w, y):
 
     """
     return w.sparse * y
+
+
+def lag_categorical(w, y, ties='tryself'):
+    """
+    Spatial lag operator for categorical variables.
+
+    Constructs the most common categories of neighboring observations, weighted
+    by their weight strength.
+
+    Parameters
+    ----------
+
+    w                   : W
+                          PySAL spatial weightsobject
+    y                   : iterable
+                          iterable collection of categories (either int or
+                          string) with dimensionality conforming to w (see examples)
+    ties                : str
+                          string describing the method to use when resolving
+                          ties. By default, the option is "tryself",
+                          and the category of the focal observation
+                          is included with its neighbors to try
+                          and break a tie. If this does not resolve the tie,
+                          a winner is chosen randomly. To just use random choice to
+                          break ties, pass "random" instead.
+    Returns
+    -------
+    an (n x k) column vector containing the most common neighboring observation
+
+    Notes
+    -----
+    This works on any array where the number of unique elements along the column
+    axis is less than the number of elements in the array, for any dtype.
+    That means the routine should work on any dtype that np.unique() can
+    compare.
+
+    Examples
+    --------
+
+    Set up a 9x9 weights matrix describing a 3x3 regular lattice. Lag one list of
+    categorical variables with no ties.
+
+    >>> import pysal
+    >>> import numpy as np
+    >>> w = pysal.lat2W(3, 3)
+    >>> y = ['a','b','a','b','c','b','c','b','c']
+    >>> y_l = pysal.weights.spatial_lag.lag_categorical(w, y)
+    >>> y_l
+    array(['b', 'a', 'b', 'c', 'b', 'c', 'b', 'c', 'b'], dtype='|S1')
+
+    Explicitly reshape y into a (9x1) array and calculate lag again
+
+    >>> yvect = np.array(y).reshape(9,1)
+    >>> yvect_l = pysal.weights.spatial_lag.lag_categorical(w,yvect)
+    array([['b'],
+           ['a'],
+           ['b'],
+           ['c'],
+           ['b'],
+           ['c'],
+           ['b'],
+           ['c'],
+           ['b']],
+          dtype='|S1')
+
+    compute the lag of a 9x2 matrix of categories
+
+    >>> y2 = ['a', 'c', 'c', 'd', 'b', 'a', 'd', 'd', 'c']
+    >>> ym = np.vstack((y,y2)).T
+    >>> ym_lag = pysal.weights.spatial_lag.lag_categorical(w,ym)
+    >>> ym_lag
+    array([['b', 'b'],
+	   ['a', 'c'],
+	   ['b', 'c'],
+	   ['c', 'd'],
+	   ['b', 'd'],
+	   ['c', 'c'],
+	   ['c', 'd'],
+	   ['c', 'd'],
+	   ['b', 'b']],
+	  dtype='|S1')
+    """
+    if isinstance(y, list):
+        y = np.array(y)
+    orig_shape = y.shape
+    if len(orig_shape) > 1:
+        if orig_shape[1] > 1:
+            return np.vstack([lag_categorical(w,col) for col in y.T]).T
+    y = y.flatten()
+    output = np.zeros_like(y)
+    keys = np.unique(y)
+    inty = np.zeros(y.shape, dtype=np.int)
+    for i,key in enumerate(keys):
+       inty[y == key] = i
+    for idx,neighbors in w:
+        vals = np.zeros(keys.shape)
+        for neighb, weight in diter(neighbors):
+            vals[inty[w.id2i[neighb]]] += weight
+        outidx = _resolve_ties(idx,inty,vals,neighbors,ties, w)
+        output[w.id2i[idx]] = keys[outidx]
+    return output.reshape(orig_shape)
+
+def _resolve_ties(i,inty,vals,neighbors,method,w):
+    """
+    Helper function to resolve ties if lag is multimodal
+
+    first, if this function gets called when there's actually no tie, then the
+    correct value will be picked.
+
+    if 'random' is selected as the method, a random tiebeaker is picked
+
+    if 'tryself' is selected, then the observation's own value will be used in
+    an attempt to break the tie, but if it fails, a random tiebreaker will be
+    selected.
+    """
+    if len(vals[vals==vals.max()]) <= 1:
+        return np.argmax(vals)
+    elif method.lower() == 'random':
+        ties = np.where(vals == vals.max())
+        return np.random.choice(vals[ties])
+    elif method.lower() == 'tryself':
+        vals[inty[w.id2i[i]]] += np.mean(neighbors.values())
+        return _resolve_ties(i,inty,vals,neighbors,'random', w)
+
diff --git a/pysal/weights/tests/test_spatial_lag.py b/pysal/weights/tests/test_spatial_lag.py
index 8883857..e841903 100644
--- a/pysal/weights/tests/test_spatial_lag.py
+++ b/pysal/weights/tests/test_spatial_lag.py
@@ -5,7 +5,7 @@ import pysal
 import numpy as np
 
 
-class Testlag_spatial(unittest.TestCase):
+class Test_spatial_lag(unittest.TestCase):
     def setUp(self):
         self.neighbors = {'c': ['b'], 'b': ['c', 'a'], 'a': ['b']}
         self.weights = {'c': [1.0], 'b': [1.0, 1.0], 'a': [1.0]}
@@ -13,7 +13,11 @@ class Testlag_spatial(unittest.TestCase):
         self.weights = {'c': [1.0], 'b': [1.0, 1.0], 'a': [1.0]}
         self.w = pysal.W(self.neighbors, self.weights, self.id_order)
         self.y = np.array([0, 1, 2])
-
+        self.wlat = pysal.lat2W(3, 3)
+        self.ycat = ['a','b','a','b','c','b','c','b','c']
+        self.ycat2 = ['a', 'c', 'c', 'd', 'b', 'a', 'd', 'd', 'c']
+        self.ym = np.vstack((self.ycat,self.ycat2)).T
+        self.random_seed = 503
     def test_lag_spatial(self):
         yl = pysal.lag_spatial(self.w, self.y)
         np.testing.assert_array_almost_equal(yl, [1., 2., 1.])
@@ -32,9 +36,25 @@ class Testlag_spatial(unittest.TestCase):
             [2., 2., 3., 3.33333333, 4.,
              4.66666667, 5., 6., 6.])
         np.testing.assert_array_almost_equal(yl, ylc)
+    
+    def test_lag_categorical(self):
+        yl = pysal.weights.spatial_lag.lag_categorical(self.wlat, self.ycat)
+        np.random.seed(self.random_seed)
+        known = np.array(['b', 'a', 'b', 'c', 'b', 'c', 'b', 'c', 'b'])
+        np.testing.assert_array_equal(yl, known)
+        ym_lag = pysal.weights.spatial_lag.lag_categorical(self.wlat,self.ym)
+        known = np.array([['b', 'b'],
+                          ['a', 'c'],
+                          ['b', 'c'],
+                          ['c', 'd'],
+                          ['b', 'd'],
+                          ['c', 'c'],
+                          ['b', 'd'],
+                          ['c', 'd'],
+                          ['b', 'b']])
+        np.testing.assert_array_equal(ym_lag, np.asarray(known))
 
-
-suite = unittest.TestLoader().loadTestsFromTestCase(Testlag_spatial)
+suite = unittest.TestLoader().loadTestsFromTestCase(Test_spatial_lag)
 
 if __name__ == '__main__':
     runner = unittest.TextTestRunner()
diff --git a/pysal/weights/tests/test_user.py b/pysal/weights/tests/test_user.py
index c6d37c1..cfcfaef 100644
--- a/pysal/weights/tests/test_user.py
+++ b/pysal/weights/tests/test_user.py
@@ -2,6 +2,7 @@ import os
 import unittest
 import pysal
 import numpy as np
+from pysal.weights.util import neighbor_equality
 
 
 class Testuser(unittest.TestCase):
@@ -59,11 +60,11 @@ class Testuser(unittest.TestCase):
         w = pysal.threshold_binaryW_from_array(points, threshold=11.2)
         self.assertEquals(w.weights, {0: [1, 1], 1: [1, 1], 2: [],
                                       3: [1, 1], 4: [1], 5: [1]})
-        self.assertEquals(w.neighbors, {0: [1, 3], 1: [0, 3], 2: [
-        ], 3: [1, 0], 4: [5], 5: [4]})
+        self.assertTrue(neighbor_equality(w, pysal.W({0: [1, 3], 1: [0, 3],
+                                                        2: [ ], 3: [0, 1],
+                                                        4: [5], 5: [4]})))
 
     def test_threshold_binaryW_from_shapefile(self):
-
         w = pysal.threshold_binaryW_from_shapefile(pysal.examples.get_path(
             "columbus.shp"), 0.62, idVariable="POLYID")
         self.assertEquals(w.weights[1], [1, 1])
diff --git a/pysal/weights/util.py b/pysal/weights/util.py
index 5ddfe94..98e1178 100644
--- a/pysal/weights/util.py
+++ b/pysal/weights/util.py
@@ -782,7 +782,7 @@ def full2W(m, ids=None):
 
 
 def WSP2W(wsp, silent_island_warning=False):
-    
+
     """
     Convert a pysal WSP object (thin weights matrix) to a pysal W object.
 
@@ -1197,6 +1197,65 @@ def write_gal(file, k=10):
         f.write(" ".join(map(str, neighs)))
     f.close()
 
+def neighbor_equality(w1, w2):
+    """
+    Test if the neighbor sets are equal between two weights objects
+
+    Parameters
+    ----------
+
+    w1 : W
+        instance of spatial weights class W
+
+    w2 : W
+        instance of spatial weights class W
+
+    Returns
+    -------
+    Boolean
+
+
+    Notes
+    -----
+    Only set membership is evaluated, no check of the weight values is carried out.
+
+
+    Examples
+    --------
+    >>> from pysal.weights.util import neighbor_equality
+    >>> w1 = pysal.lat2W(3,3)
+    >>> w2 = pysal.lat2W(3,3)
+    >>> neighbor_equality(w1, w2)
+    True
+    >>> w3 = pysal.lat2W(5,5)
+    >>> neighbor_equality(w1, w3)
+    False
+    >>> n4 = w1.neighbors.copy()
+    >>> n4[0] = [1]
+    >>> n4[1] = [4, 2]
+    >>> w4 = pysal.W(n4)
+    >>> neighbor_equality(w1, w4)
+    False
+    >>> n5 = w1.neighbors.copy()
+    >>> n5[0]
+    [3, 1]
+    >>> n5[0] = [1, 3]
+    >>> w5 = pysal.W(n5)
+    >>> neighbor_equality(w1, w5)
+    True
+
+    """
+    n1 = w1.neighbors
+    n2 = w2.neighbors
+    ids_1 = set(n1.keys())
+    ids_2 = set(n2.keys())
+    if ids_1 != ids_2:
+        return False
+    for i in ids_1:
+        if set(w1.neighbors[i]) != set(w2.neighbors[i]):
+            return False
+    return True
+
 if __name__ == "__main__":
     from pysal import lat2W
 
@@ -1205,4 +1264,5 @@ if __name__ == "__main__":
     assert (lat2W(5, 3, rook=False).sparse.todense() == lat2SW(5, 3,
                                                                'queen').todense()).all()
     assert (lat2W(50, 50, rook=False).sparse.todense() == lat2SW(50,
+
                                                                  50, 'queen').todense()).all()
diff --git a/pysal/weights/weights.py b/pysal/weights/weights.py
index 9bb522e..11788d5 100644
--- a/pysal/weights/weights.py
+++ b/pysal/weights/weights.py
@@ -124,18 +124,18 @@ class W(object):
 
     >>> neighbors = {0: [3, 1], 1: [0, 4, 2], 2: [1, 5], 3: [0, 6, 4], 4: [1, 3, 7, 5], 5: [2, 4, 8], 6: [3, 7], 7: [4, 6, 8], 8: [5, 7]}
     >>> w = W(neighbors)
-    >>> "%.3f"%w.pct_nonzero
-    '29.630'
+    >>> round(w.pct_nonzero,3)
+    29.63
     >>> w = lat2W(100, 100)
     >>> w.trcW2
     39600.0
     >>> w.trcWtW
     39600.0
     >>> w.transform='r'
-    >>> w.trcW2
-    2530.7222222222586
-    >>> w.trcWtW
-    2533.6666666666774
+    >>> round(w.trcW2, 3)
+    2530.722
+    >>> round(w.trcWtW, 3)
+    2533.667
 
     Cardinality Histogram
 
diff --git a/setup.py b/setup.py
index ccad7df..c270edc 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@ with open('README.rst') as file:
 
 MAJOR = 1
 MINOR = 11
-MICRO = 0
+MICRO = 1
 ISRELEASED = False
 VERSION = '%d.%d.%d' % (MAJOR, MINOR, MICRO)
 
diff --git a/touch.txt b/touch.txt
deleted file mode 100644
index 8bd6648..0000000
--- a/touch.txt
+++ /dev/null
@@ -1 +0,0 @@
-asdf

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/pysal.git



More information about the Pkg-grass-devel mailing list