Beispiel #1
0
    def choropleth(self,
                   geo_data,
                   data=None,
                   columns=None,
                   key_on=None,
                   threshold_scale=None,
                   fill_color='blue',
                   fill_opacity=0.6,
                   line_color='black',
                   line_weight=1,
                   line_opacity=1,
                   name=None,
                   legend_name='',
                   topojson=None,
                   reset=False,
                   smooth_factor=None,
                   highlight=None):
        """
        Apply a GeoJSON overlay to the map.

        Plot a GeoJSON overlay on the base map. There is no requirement
        to bind data (passing just a GeoJSON plots a single-color overlay),
        but there is a data binding option to map your columnar data to
        different feature objects with a color scale.

        If data is passed as a Pandas DataFrame, the "columns" and "key-on"
        keywords must be included, the first to indicate which DataFrame
        columns to use, the second to indicate the layer in the GeoJSON
        on which to key the data. The 'columns' keyword does not need to be
        passed for a Pandas series.

        Colors are generated from color brewer (http://colorbrewer2.org/)
        sequential palettes on a D3 threshold scale. The scale defaults to the
        following quantiles: [0, 0.5, 0.75, 0.85, 0.9]. A custom scale can be
        passed to `threshold_scale` of length <=6, in order to match the
        color brewer range.

        TopoJSONs can be passed as "geo_data", but the "topojson" keyword must
        also be passed with the reference to the topojson objects to convert.
        See the topojson.feature method in the TopoJSON API reference:
        https://github.com/topojson/topojson/wiki/API-Reference


        Parameters
        ----------
        geo_data: string/object
            URL, file path, or data (json, dict, geopandas, etc) to your GeoJSON geometries
        data: Pandas DataFrame or Series, default None
            Data to bind to the GeoJSON.
        columns: dict or tuple, default None
            If the data is a Pandas DataFrame, the columns of data to be bound.
            Must pass column 1 as the key, and column 2 the values.
        key_on: string, default None
            Variable in the GeoJSON file to bind the data to. Must always
            start with 'feature' and be in JavaScript objection notation.
            Ex: 'feature.id' or 'feature.properties.statename'.
        threshold_scale: list, default None
            Data range for D3 threshold scale. Defaults to the following range
            of quantiles: [0, 0.5, 0.75, 0.85, 0.9], rounded to the nearest
            order-of-magnitude integer. Ex: 270 rounds to 200, 5600 to 6000.
        fill_color: string, default 'blue'
            Area fill color. Can pass a hex code, color name, or if you are
            binding data, one of the following color brewer palettes:
            'BuGn', 'BuPu', 'GnBu', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'RdPu',
            'YlGn', 'YlGnBu', 'YlOrBr', and 'YlOrRd'.
        fill_opacity: float, default 0.6
            Area fill opacity, range 0-1.
        line_color: string, default 'black'
            GeoJSON geopath line color.
        line_weight: int, default 1
            GeoJSON geopath line weight.
        line_opacity: float, default 1
            GeoJSON geopath line opacity, range 0-1.
        legend_name: string, default empty string
            Title for data legend.
        topojson: string, default None
            If using a TopoJSON, passing "objects.yourfeature" to the topojson
            keyword argument will enable conversion to GeoJSON.
        reset: boolean, default False
            Remove all current geoJSON layers, start with new layer
        smooth_factor: float, default None
            How much to simplify the polyline on each zoom level. More means
            better performance and smoother look, and less means more accurate
            representation. Leaflet defaults to 1.0.
        highlight: boolean, default False
            Enable highlight functionality when hovering over a GeoJSON area.

        Returns
        -------
        GeoJSON data layer in obj.template_vars

        Examples
        --------
        >>> m.choropleth(geo_data='us-states.json', line_color='blue',
        ...              line_weight=3)
        >>> m.choropleth(geo_data='geo.json', data=df,
        ...              columns=['Data 1', 'Data 2'],
        ...              key_on='feature.properties.myvalue',
        ...              fill_color='PuBu',
        ...              threshold_scale=[0, 20, 30, 40, 50, 60])
        >>> m.choropleth(geo_data='countries.json',
        ...              topojson='objects.countries')
        >>> m.choropleth(geo_data='geo.json', data=df,
        ...              columns=['Data 1', 'Data 2'],
        ...              key_on='feature.properties.myvalue',
        ...              fill_color='PuBu',
        ...              threshold_scale=[0, 20, 30, 40, 50, 60],
        ...              highlight=True)

        """
        if threshold_scale and len(threshold_scale) > 6:
            raise ValueError
        if data is not None and not color_brewer(fill_color):
            raise ValueError('Please pass a valid color brewer code to '
                             'fill_local. See docstring for valid codes.')

        # Create color_data dict
        if hasattr(data, 'set_index'):
            # This is a pd.DataFrame
            color_data = data.set_index(columns[0])[columns[1]].to_dict()
        elif hasattr(data, 'to_dict'):
            # This is a pd.Series
            color_data = data.to_dict()
        elif data:
            color_data = dict(data)
        else:
            color_data = None

        # Compute color_domain
        if threshold_scale:
            color_domain = list(threshold_scale)
        elif color_data:
            # To avoid explicit pandas dependency ; changed default behavior.
            data_min = min(color_data.values())
            data_max = max(color_data.values())
            if data_min == data_max:
                data_min = (data_min
                            if data_min < 0 else 0 if data_min > 0 else -1)
                data_max = (data_max
                            if data_max > 0 else 0 if data_max < 0 else 1)
            data_min, data_max = (1.01 * data_min - 0.01 * data_max,
                                  1.01 * data_max - 0.01 * data_min)
            nb_class = 6
            color_domain = [
                data_min + i * (data_max - data_min) * 1. / nb_class
                for i in range(1 + nb_class)
            ]
        else:
            color_domain = None

        if color_domain and key_on:
            key_on = key_on[8:] if key_on.startswith('feature.') else key_on
            color_range = color_brewer(fill_color, n=len(color_domain))

            def get_by_key(obj, key):
                return (obj.get(key, None) if len(key.split('.')) <= 1 else
                        get_by_key(obj.get(key.split('.')[0], None), '.'.join(
                            key.split('.')[1:])))

            def color_scale_fun(x):
                return color_range[len([
                    u for u in color_domain
                    if get_by_key(x, key_on) in color_data
                    and u <= color_data[get_by_key(x, key_on)]
                ])]
        else:

            def color_scale_fun(x):
                return fill_color

        def style_function(x):
            return {
                'weight': line_weight,
                'opacity': line_opacity,
                'color': line_color,
                'fillOpacity': fill_opacity,
                'fillColor': color_scale_fun(x)
            }

        def highlight_function(x):
            return {
                'weight': line_weight + 2,
                'fillOpacity': fill_opacity + .2
            }

        if topojson:
            geo_json = TopoJson(geo_data,
                                topojson,
                                name=name,
                                style_function=style_function,
                                smooth_factor=smooth_factor)
        else:
            geo_json = GeoJson(
                geo_data,
                name=name,
                style_function=style_function,
                smooth_factor=smooth_factor,
                highlight_function=highlight_function if highlight else None)

        self.add_child(geo_json)

        # Create ColorMap.
        if color_domain:
            brewed = color_brewer(fill_color, n=len(color_domain))
            color_scale = StepColormap(
                brewed[1:len(color_domain)],
                index=color_domain,
                vmin=color_domain[0],
                vmax=color_domain[-1],
                caption=legend_name,
            )
            self.add_child(color_scale)
Beispiel #2
0
    def choropleth(self,
                   geo_data,
                   data=None,
                   columns=None,
                   key_on=None,
                   bins=6,
                   fill_color='blue',
                   nan_fill_color='black',
                   fill_opacity=0.6,
                   nan_fill_opacity=None,
                   line_color='black',
                   line_weight=1,
                   line_opacity=1,
                   name=None,
                   legend_name='',
                   topojson=None,
                   reset=False,
                   smooth_factor=None,
                   highlight=None,
                   **kwargs):
        """
        Apply a GeoJSON overlay to the map.

        Plot a GeoJSON overlay on the base map. There is no requirement
        to bind data (passing just a GeoJSON plots a single-color overlay),
        but there is a data binding option to map your columnar data to
        different feature objects with a color scale.

        If data is passed as a Pandas DataFrame, the "columns" and "key-on"
        keywords must be included, the first to indicate which DataFrame
        columns to use, the second to indicate the layer in the GeoJSON
        on which to key the data. The 'columns' keyword does not need to be
        passed for a Pandas series.

        Colors are generated from color brewer (http://colorbrewer2.org/)
        sequential palettes. By default, linear binning is used between
        the min and the max of the values. Custom binning can be achieved
        with the `bins` parameter.

        TopoJSONs can be passed as "geo_data", but the "topojson" keyword must
        also be passed with the reference to the topojson objects to convert.
        See the topojson.feature method in the TopoJSON API reference:
        https://github.com/topojson/topojson/wiki/API-Reference


        Parameters
        ----------
        geo_data: string/object
            URL, file path, or data (json, dict, geopandas, etc) to your GeoJSON
            geometries
        data: Pandas DataFrame or Series, default None
            Data to bind to the GeoJSON.
        columns: dict or tuple, default None
            If the data is a Pandas DataFrame, the columns of data to be bound.
            Must pass column 1 as the key, and column 2 the values.
        key_on: string, default None
            Variable in the `geo_data` GeoJSON file to bind the data to. Must
            start with 'feature' and be in JavaScript objection notation.
            Ex: 'feature.id' or 'feature.properties.statename'.
        bins: int or sequence of scalars or str, default 6
            If `bins` is an int, it defines the number of equal-width
            bins between the min and the max of the values.
            If `bins` is a sequence, it directly defines the bin edges.
            For more information on this parameter, have a look at
            numpy.histogram function.
        fill_color: string, default 'blue'
            Area fill color. Can pass a hex code, color name, or if you are
            binding data, one of the following color brewer palettes:
            'BuGn', 'BuPu', 'GnBu', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'RdPu',
            'YlGn', 'YlGnBu', 'YlOrBr', and 'YlOrRd'.
        nan_fill_color: string, default 'black'
            Area fill color for nan or missing values.
            Can pass a hex code, color name.
        fill_opacity: float, default 0.6
            Area fill opacity, range 0-1.
        nan_fill_opacity: float, default fill_opacity
            Area fill opacity for nan or missing values, range 0-1.
        line_color: string, default 'black'
            GeoJSON geopath line color.
        line_weight: int, default 1
            GeoJSON geopath line weight.
        line_opacity: float, default 1
            GeoJSON geopath line opacity, range 0-1.
        legend_name: string, default empty string
            Title for data legend.
        topojson: string, default None
            If using a TopoJSON, passing "objects.yourfeature" to the topojson
            keyword argument will enable conversion to GeoJSON.
        reset: boolean, default False
            Remove all current geoJSON layers, start with new layer
        smooth_factor: float, default None
            How much to simplify the polyline on each zoom level. More means
            better performance and smoother look, and less means more accurate
            representation. Leaflet defaults to 1.0.
        highlight: boolean, default False
            Enable highlight functionality when hovering over a GeoJSON area.

        Returns
        -------
        GeoJSON data layer in obj.template_vars

        Examples
        --------
        >>> m.choropleth(geo_data='us-states.json', line_color='blue',
        ...              line_weight=3)
        >>> m.choropleth(geo_data='geo.json', data=df,
        ...              columns=['Data 1', 'Data 2'],
        ...              key_on='feature.properties.myvalue',
        ...              fill_color='PuBu',
        ...              bins=[0, 20, 30, 40, 50, 60])
        >>> m.choropleth(geo_data='countries.json',
        ...              topojson='objects.countries')
        >>> m.choropleth(geo_data='geo.json', data=df,
        ...              columns=['Data 1', 'Data 2'],
        ...              key_on='feature.properties.myvalue',
        ...              fill_color='PuBu',
        ...              bins=[0, 20, 30, 40, 50, 60],
        ...              highlight=True)

        """
        if data is not None and not color_brewer(fill_color):
            raise ValueError('Please pass a valid color brewer code to '
                             'fill_local. See docstring for valid codes.')

        if nan_fill_opacity is None:
            nan_fill_opacity = fill_opacity

        if 'threshold_scale' in kwargs:
            if kwargs['threshold_scale'] is not None:
                bins = kwargs['threshold_scale']
            warnings.warn(
                'choropleth `threshold_scale` parameter is now depreciated '
                'in favor of the `bins` parameter.', DeprecationWarning)

        # Create color_data dict
        if hasattr(data, 'set_index'):
            # This is a pd.DataFrame
            color_data = data.set_index(columns[0])[columns[1]].to_dict()
        elif hasattr(data, 'to_dict'):
            # This is a pd.Series
            color_data = data.to_dict()
        elif data:
            color_data = dict(data)
        else:
            color_data = None

        if color_data is not None and key_on is not None:
            real_values = np.array(list(color_data.values()))
            real_values = real_values[~np.isnan(real_values)]
            _, bin_edges = np.histogram(real_values, bins=bins)

            bins_min, bins_max = min(bin_edges), max(bin_edges)
            if np.any((real_values < bins_min) | (real_values > bins_max)):
                raise ValueError(
                    'All values are expected to fall into one of the provided '
                    'bins (or to be Nan). Please check the `bins` parameter '
                    'and/or your data.')

            # We add the colorscale
            nb_bins = len(bin_edges) - 1
            color_range = color_brewer(fill_color, n=nb_bins)
            color_scale = StepColormap(color_range,
                                       index=bin_edges,
                                       vmin=bins_min,
                                       vmax=bins_max,
                                       caption=legend_name)
            self.add_child(color_scale)

            # then we 'correct' the last edge for numpy digitize
            # (we add a very small amount to fake an inclusive right interval)
            increasing = bin_edges[0] <= bin_edges[-1]
            bin_edges[-1] = np.nextafter(bin_edges[-1],
                                         (1 if increasing else -1) * np.inf)

            key_on = key_on[8:] if key_on.startswith('feature.') else key_on

            def get_by_key(obj, key):
                return (obj.get(key, None) if len(key.split('.')) <= 1 else
                        get_by_key(obj.get(key.split('.')[0], None), '.'.join(
                            key.split('.')[1:])))

            def color_scale_fun(x):
                key_of_x = get_by_key(x, key_on)

                if key_of_x not in color_data.keys():
                    return nan_fill_color, nan_fill_opacity

                value_of_x = color_data[key_of_x]
                if np.isnan(value_of_x):
                    return nan_fill_color, nan_fill_opacity

                color_idx = np.digitize(value_of_x, bin_edges, right=False) - 1
                return color_range[color_idx], fill_opacity

        else:

            def color_scale_fun(x):
                return fill_color, fill_opacity

        def style_function(x):
            color, opacity = color_scale_fun(x)
            return {
                'weight': line_weight,
                'opacity': line_opacity,
                'color': line_color,
                'fillOpacity': opacity,
                'fillColor': color
            }

        def highlight_function(x):
            return {
                'weight': line_weight + 2,
                'fillOpacity': fill_opacity + .2
            }

        if topojson:
            geo_json = TopoJson(geo_data,
                                topojson,
                                name=name,
                                style_function=style_function,
                                smooth_factor=smooth_factor)
        else:
            geo_json = GeoJson(
                geo_data,
                name=name,
                style_function=style_function,
                smooth_factor=smooth_factor,
                highlight_function=highlight_function if highlight else None)

        self.add_child(geo_json)