Пример #1
0
    def __init__(self, gdf, block_id, spatial_weights=None):
        self.gdf = gdf

        results_list = []
        gdf = gdf.copy()

        if not isinstance(block_id, str):
            gdf["mm_bid"] = block_id
            block_id = "mm_bid"

        self.block_id = gdf[block_id]
        # if weights matrix is not passed, generate it from objects
        if spatial_weights is None:
            print("Calculating spatial weights...")
            from libpysal.weights import Queen

            spatial_weights = Queen.from_dataframe(gdf, silence_warnings=True)

        self.sw = spatial_weights
        # dict to store nr of courtyards for each uID
        courtyards = {}
        components = pd.Series(spatial_weights.component_labels,
                               index=gdf.index)
        for index in tqdm(gdf.index, total=gdf.shape[0]):
            # if the id is already present in courtyards, continue (avoid repetition)
            if index in courtyards:
                continue
            else:
                comp = spatial_weights.component_labels[index]
                to_join = components[components == comp].index
                joined = gdf.loc[to_join]
                dissolved = joined.geometry.buffer(
                    0.01
                ).unary_union  # buffer to avoid multipolygons where buildings touch by corners only
                try:
                    interiors = len(list(dissolved.interiors))
                except (ValueError):
                    print("Something unexpected happened.")
                for b in to_join:
                    courtyards[b] = interiors  # fill dict with values
        # copy values from dict to gdf
        for index, row in tqdm(gdf.iterrows(), total=gdf.shape[0]):
            results_list.append(courtyards[index])

        self.series = pd.Series(results_list, index=gdf.index)
Пример #2
0
    def __init__(self,
                 gdf,
                 spatial_weights_higher,
                 unique_id,
                 spatial_weights=None,
                 verbose=True):
        self.gdf = gdf
        self.sw_higher = spatial_weights_higher
        self.id = gdf[unique_id]
        results_list = []

        # if weights matrix is not passed, generate it from gdf
        if spatial_weights is None:
            print("Calculating spatial weights...") if verbose else None
            from libpysal.weights import Queen

            spatial_weights = Queen.from_dataframe(gdf,
                                                   silence_warnings=True,
                                                   ids=unique_id)
            print("Spatial weights ready...") if verbose else None

        self.sw = spatial_weights
        patches = dict(zip(gdf[unique_id], spatial_weights.component_labels))

        for uid in tqdm(
                self.id,
                total=gdf.shape[0],
                disable=not verbose,
                desc="Calculating adjacency",
        ):
            if uid in spatial_weights_higher.neighbors.keys():
                neighbours = spatial_weights_higher.neighbors[uid].copy()
                if neighbours:
                    neighbours.append(uid)

                    patches_sub = [patches[x] for x in neighbours]
                    patches_nr = len(set(patches_sub))

                    results_list.append(patches_nr / len(neighbours))
                else:
                    results_list.append(np.nan)
            else:
                results_list.append(np.nan)

        self.series = pd.Series(results_list, index=gdf.index)
Пример #3
0
    def __init__(self, gdf, block_id=None, spatial_weights=None, verbose=True):
        if block_id is not None:
            warnings.warn(
                "block_id is deprecated and will be removed in v0.4.", FutureWarning,
            )
        self.gdf = gdf

        results_list = []
        gdf = gdf.copy()

        # if weights matrix is not passed, generate it from objects
        if spatial_weights is None:
            print("Calculating spatial weights...") if verbose else None
            from libpysal.weights import Queen

            spatial_weights = Queen.from_dataframe(gdf, silence_warnings=True)

        self.sw = spatial_weights
        # dict to store nr of courtyards for each uID
        courtyards = {}
        components = pd.Series(spatial_weights.component_labels, index=gdf.index)
        for i, index in tqdm(
            enumerate(gdf.index), total=gdf.shape[0], disable=not verbose
        ):
            # if the id is already present in courtyards, continue (avoid repetition)
            if index in courtyards:
                continue
            else:
                comp = spatial_weights.component_labels[i]
                to_join = components[components == comp].index
                joined = gdf.loc[to_join]
                dissolved = joined.geometry.buffer(
                    0.01
                ).unary_union  # buffer to avoid multipolygons where buildings touch by corners only
                try:
                    interiors = len(list(dissolved.interiors))
                except (ValueError):
                    print("Something unexpected happened.")
                for b in to_join:
                    courtyards[b] = interiors  # fill dict with values

        results_list = [courtyards[index] for index in gdf.index]

        self.series = pd.Series(results_list, index=gdf.index)
Пример #4
0
 def test_BuildingAdjacencyy(self):
     sw = Queen.from_dataframe(self.df_buildings, ids="uID")
     swh = mm.sw_high(k=3, gdf=self.df_tessellation, ids="uID")
     self.df_buildings["adj_sw"] = mm.BuildingAdjacency(
         self.df_buildings,
         spatial_weights=sw,
         unique_id="uID",
         spatial_weights_higher=swh,
     ).series
     self.df_buildings["adj_sw_none"] = mm.BuildingAdjacency(
         self.df_buildings, unique_id="uID",
         spatial_weights_higher=swh).series
     check = 0.2613824113909074
     assert self.df_buildings["adj_sw"].mean() == check
     assert self.df_buildings["adj_sw_none"].mean() == check
     swh_drop = mm.sw_high(k=3, gdf=self.df_tessellation[2:], ids="uID")
     assert (mm.BuildingAdjacency(
         self.df_buildings,
         unique_id="uID",
         spatial_weights_higher=swh_drop).series.isna().any())
Пример #5
0
    def __init__(self, gdf, spatial_weights=None, verbose=True):
        self.gdf = gdf

        if spatial_weights is None:

            print("Calculating spatial weights...") if verbose else None
            from libpysal.weights import Queen

            spatial_weights = Queen.from_dataframe(gdf, silence_warnings=True)
            print("Spatial weights ready...") if verbose else None
        self.sw = spatial_weights

        # dict to store walls for each uID
        walls = {}
        components = pd.Series(spatial_weights.component_labels,
                               index=range(len(gdf)))
        geom = gdf.geometry

        for i in tqdm(range(gdf.shape[0]),
                      total=gdf.shape[0],
                      disable=not verbose):
            # if the id is already present in walls, continue (avoid repetition)
            if i in walls:
                continue
            else:
                comp = spatial_weights.component_labels[i]
                to_join = components[components == comp].index
                joined = geom.iloc[to_join]
                dissolved = joined.buffer(
                    0.01
                ).unary_union  # buffer to avoid multipolygons where buildings touch by corners only
                for b in to_join:
                    walls[b] = dissolved.exterior.length

        results_list = []
        for i in tqdm(range(gdf.shape[0]),
                      total=gdf.shape[0],
                      disable=not verbose):
            results_list.append(walls[i])
        self.series = pd.Series(results_list, index=gdf.index)
Пример #6
0
    def __init__(self,
                 gdf,
                 spatial_weights_higher,
                 unique_id,
                 spatial_weights=None):
        self.gdf = gdf
        self.sw_higher = spatial_weights_higher
        self.id = gdf[unique_id]
        results_list = []

        # if weights matrix is not passed, generate it from gdf
        if spatial_weights is None:
            print("Calculating spatial weights...")
            from libpysal.weights import Queen

            spatial_weights = Queen.from_dataframe(gdf,
                                                   silence_warnings=True,
                                                   ids=unique_id)
            print("Spatial weights ready...")

        self.sw = spatial_weights
        patches = dict(zip(gdf[unique_id], spatial_weights.component_labels))

        print("Calculating adjacency...")
        for index, row in tqdm(gdf.iterrows(), total=gdf.shape[0]):
            neighbours = spatial_weights_higher.neighbors[
                row[unique_id]].copy()
            if neighbours:
                neighbours.append(row[unique_id])

                patches_sub = [patches[x] for x in neighbours]
                patches_nr = len(set(patches_sub))

                results_list.append(patches_nr / len(neighbours))
            else:
                results_list.append(0)

        self.series = pd.Series(results_list, index=gdf.index)
Пример #7
0
import ols as OLS
from utils import optim_moments, RegressionPropsY, get_spFilter, spdot
# import user_output as USER
# import summary_output as SUMMARY
# import regimes as REGI

# First import libpysal to load the spatial analysis tools.
import libpysal
from libpysal.examples import load_example
from libpysal.weights import Queen

# Open data on NCOVR US County Homicides (3085 areas).
nat = load_example('Natregimes')
db = libpysal.io.open(nat.get_path('natregimes.dbf'),'r')
nat_shp = libpysal.examples.get_path("natregimes.shp")
w = Queen.from_shapefile(nat_shp)
w.transform = 'r'

name_y = ['HR70','HR80','HR90']
y = np.array([db.by_col(name) for name in name_y]).T

name_x = ['RD70','RD80','RD90','PS70','PS80','PS90']
x = np.array([db.by_col(name) for name in name_x]).T

full_weights = False


def _moments_kkp(ws, u, i, trace_w2=None):
    '''
    Compute G and g matrices for the KKP model.
    ...
Пример #8
0
def _spatial_dissim(data,
                    group_pop_var,
                    total_pop_var,
                    w=None,
                    standardize=False):
    """Calculate of Spatial Dissimilarity index.

    Parameters
    ----------
    data : a geopandas DataFrame with a geometry column.
    group_pop_var : string
        The name of variable in data that contains the population size of the group of interest
    total_pop_var : string
        The name of variable in data that contains the total population of the unit
    w : W
        A PySAL weights object. If not provided, Queen contiguity matrix is used.
    standardize  : boolean
        A condition for row standardisation of the weights matrices. If True, the values of cij in the formulas gets row standardized.
        For the sake of comparison, the seg R package of Hong, Seong-Yun, David O'Sullivan, and Yukio Sadahiro. "Implementing spatial segregation measures in R." PloS one 9.11 (2014): e113767.
        works by default with row standardization.

    Returns
    ----------
    statistic : float
        Spatial Dissimilarity Index
    core_data : a geopandas DataFrame
        A geopandas DataFrame that contains the columns used to perform the estimate.

    Notes
    -----
    Based on Morrill, R. L. (1991) "On the Measure of Geographic Segregation". Geography Research Forum.

    Reference: :cite:`morrill1991measure`.

    """
    if type(standardize) is not bool:
        raise TypeError("std is not a boolean object")

    if w is None:
        w_object = Queen.from_dataframe(data)
    else:
        w_object = w

    if not issubclass(type(w_object), libpysal.weights.W):
        raise TypeError("w is not a PySAL weights object")

    D = _dissim(data, group_pop_var, total_pop_var)[0]

    x = np.array(data[group_pop_var])
    t = np.array(data[total_pop_var])

    # If a unit has zero population, the group of interest frequency is zero
    pi = np.where(t == 0, 0, x / t)

    if not standardize:
        cij = w_object.sparse.toarray()
    else:
        cij = w_object.sparse.toarray()
        cij = cij / cij.sum(axis=1).reshape((cij.shape[0], 1))

    # Inspired in (second solution): https://stackoverflow.com/questions/22720864/efficiently-calculating-a-euclidean-distance-matrix-using-numpy
    # Distance Matrix
    abs_dist = abs(pi[..., np.newaxis] - pi)

    # manhattan_distances used to compute absolute distances
    num = np.multiply(abs_dist, cij).sum()
    den = cij.sum()
    SD = D - num / den
    SD

    core_data = data[[group_pop_var, total_pop_var, data.geometry.name]]

    return SD, core_data
Пример #9
0
def mean_interbuilding_distance(objects,
                                tessellation,
                                unique_id,
                                spatial_weights=None,
                                spatial_weights_higher=None,
                                order=3):
    """
    Calculate the mean interbuilding distance within x topological steps

    Interbuilding distances are calculated between buildings on adjacent cells based on `spatial_weights`.

    .. math::


    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing objects to analyse
    tessellation : GeoDataFrame
        GeoDataFrame containing morphological tessellation - source of spatial_weights and spatial_weights_higher.
        It is crucial to use exactly same input as was used durign the calculation of weights matrix and spatial_weights_higher.
        If spatial_weights or spatial_weights_higher is None, tessellation is used to calulate it.
    unique_id : str
        name of the column with unique id
    spatial_weights : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity matrix will be calculated
        based on tessellation
    spatial_weights_higher : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity of higher order will be calculated
        based on tessellation
    order : int
        Order of Queen contiguity

    Returns
    -------
    Series
        Series containing resulting values.

    References
    ---------
    ADD, but it is adapted quite a lot.

    Notes
    -----
    Fix terminology, it is unclear.
    Fix UserWarning.

    Examples
    --------
    >>> buildings_df['mean_interbuilding_distance'] = momepy.mean_interbuilding_distance(buildings_df, tessellation_df, 'uID')
    Calculating mean interbuilding distances...
    Generating weights matrix (Queen)...
    Generating weights matrix (Queen) of 3 topological steps...
    Generating adjacency matrix based on weights matrix...
    Computing interbuilding distances...
    100%|██████████| 746/746 [00:03<00:00, 200.14it/s]
    Computing mean interbuilding distances...
    100%|██████████| 144/144 [00:00<00:00, 317.42it/s]
    Mean interbuilding distances calculated.
    >>> buildings_df['mean_interbuilding_distance'][0]
    29.305457092042744
    """
    if not all(tessellation.index == range(len(tessellation))):
        raise ValueError(
            'Index is not consecutive range 0:x, spatial weights will not match objects.'
        )

    print('Calculating mean interbuilding distances...')
    if spatial_weights is None:
        print('Generating weights matrix (Queen)...')
        from libpysal.weights import Queen
        # matrix to capture interbuilding relationship
        spatial_weights = Queen.from_dataframe(tessellation)

    if spatial_weights_higher is None:
        print('Generating weights matrix (Queen) of {} topological steps...'.
              format(order))
        from momepy import Queen_higher
        # matrix to define area of analysis (more steps)
        spatial_weights_higher = Queen_higher(k=order,
                                              geodataframe=tessellation)

    # define empty list for results
    results_list = []

    print('Generating adjacency matrix based on weights matrix...')
    # define adjacency list from lipysal
    adj_list = spatial_weights.to_adjlist()
    adj_list['distance'] = -1

    print('Computing interbuilding distances...')
    # measure each interbuilding distance of neighbours and save them to adjacency list
    for index, row in tqdm(adj_list.iterrows(), total=adj_list.shape[0]):
        inverted = adj_list[(adj_list.focal == row.neighbor)][(
            adj_list.neighbor == row.focal)].iloc[0]['distance']
        if inverted == -1:
            object_id = tessellation.iloc[row.focal.astype(int)][unique_id]
            building_object = objects.loc[objects[unique_id] == object_id]

            neighbours_id = tessellation.iloc[row.neighbor.astype(
                int)][unique_id]
            building_neighbour = objects.loc[objects[unique_id] ==
                                             neighbours_id]
            adj_list.loc[
                index,
                'distance'] = building_neighbour.iloc[0].geometry.distance(
                    building_object.iloc[0].geometry)
        else:
            adj_list.at[index, 'distance'] = inverted

    print('Computing mean interbuilding distances...')
    # iterate over objects to get the final values
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        # id to match spatial weights
        uid = tessellation.loc[tessellation[unique_id] ==
                               row[unique_id]].index[0]
        # define neighbours based on weights matrix defining analysis area
        neighbours = spatial_weights_higher.neighbors[uid]
        neighbours.append(uid)
        if neighbours:
            selection = adj_list[adj_list.focal.isin(neighbours)][
                adj_list.neighbor.isin(neighbours)]
            results_list.append(np.nanmean(selection.distance))

    series = pd.Series(results_list)
    print('Mean interbuilding distances calculated.')
    return series
Пример #10
0
def building_adjacency(objects,
                       tessellation,
                       spatial_weights=None,
                       spatial_weights_higher=None,
                       order=3,
                       unique_id='uID'):
    """
    Calculate the level of building adjacency

    Building adjacency reflects how much buildings tend to join together into larger structures.
    It is calculated as a ratio of joined built-up structures and buildings within k topological steps.

    .. math::


    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing objects to analyse
    tessellation : GeoDataFrame
        GeoDataFrame containing morphological tessellation - source of spatial_weights and spatial_weights_higher.
        It is crucial to use exactly same input as was used durign the calculation of weights matrix and spatial_weights_higher.
        If spatial_weights or spatial_weights_higher is None, tessellation is used to calulate it.
    spatial_weights : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity matrix will be calculated
        based on tessellation
    spatial_weights_higher : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity of higher order will be calculated
        based on tessellation
    order : int
        Order of Queen contiguity

    Returns
    -------
    Series
        Series containing resulting values.

    References
    ---------
    Vanderhaegen S and Canters F (2017) Mapping urban form and function at city
    block level using spatial metrics. Landscape and Urban Planning 167: 399–409.

    Examples
    --------
    >>> buildings_df['adjacency'] = momepy.building_adjacency(buildings_df, tessellation_df, unique_id='uID')
    Calculating adjacency...
    Calculating spatial weights...
    Spatial weights ready...
    Generating weights matrix (Queen) of 3 topological steps...
    Generating dictionary of built-up patches...
    100%|██████████| 144/144 [00:00<00:00, 9301.73it/s]
    Calculating adjacency within k steps...
    100%|██████████| 144/144 [00:00<00:00, 335.55it/s]
    Adjacency calculated.
    >>> buildings_df['adjacency'][10]
    0.23809523809523808
    """
    # define empty list for results
    results_list = []

    print('Calculating adjacency...')

    if not all(tessellation.index == range(len(tessellation))):
        raise ValueError(
            'Index is not consecutive range 0:x, spatial weights will not match objects.'
        )

    # if weights matrix is not passed, generate it from objects
    if spatial_weights is None:
        print('Calculating spatial weights...')
        from libpysal.weights import Queen
        spatial_weights = Queen.from_dataframe(objects, silence_warnings=True)
        print('Spatial weights ready...')

    if spatial_weights_higher is None:
        print('Generating weights matrix (Queen) of {} topological steps...'.
              format(order))
        from momepy import Queen_higher
        # matrix to define area of analysis (more steps)
        spatial_weights_higher = Queen_higher(k=order,
                                              geodataframe=tessellation)

    print('Generating dictionary of built-up patches...')
    # dict to store nr of courtyards for each uID
    patches = {}
    jID = 1
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):

        # if the id is already present in courtyards, continue (avoid repetition)
        if index in patches:
            continue
        else:
            to_join = [index
                       ]  # list of indices which should be joined together
            neighbours = []  # list of neighbours
            weights = spatial_weights.neighbors[
                index]  # neighbours from spatial weights
            for w in weights:
                neighbours.append(w)  # make a list from weigths

            for n in neighbours:
                while n not in to_join:  # until there is some neighbour which is not in to_join
                    to_join.append(n)
                    weights = spatial_weights.neighbors[n]
                    for w in weights:
                        neighbours.append(
                            w
                        )  # extend neighbours by neighbours of neighbours :)
            for b in to_join:
                patches[b] = jID  # fill dict with values
            jID = jID + 1

    print('Calculating adjacency within k steps...')
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        uid = tessellation.loc[tessellation[unique_id] ==
                               row[unique_id]].index[0]
        neighbours = spatial_weights_higher.neighbors[uid]

        neighbours_ids = tessellation.iloc[neighbours][unique_id]
        neighbours_ids = neighbours_ids.append(
            pd.Series(row[unique_id], index=[index]))
        building_neighbours = objects.loc[objects[unique_id].isin(
            neighbours_ids)]
        indices = list(building_neighbours.index)
        patches_sub = [patches[x] for x in indices]
        patches_nr = len(set(patches_sub))

        results_list.append(patches_nr / len(building_neighbours))

    series = pd.Series(results_list)

    print('Adjacency calculated.')
    return series
Пример #11
0
    def run_stats(self):
        """Main function which do the process."""

        # Get the common fields..currentField()
        self.admin_layer = self.cbx_aggregation_layer.currentLayer()
        input_name = self.admin_layer.name()
        field = self.cbx_indicator_field.currentField()

        self.layer = QgsProject.instance().mapLayersByName(input_name)[0]
        # Output.
        self.output_file_path = self.le_output_filepath.text()

        try:
            self.button_box_ok.setDisabled(True)
            # noinspection PyArgumentList
            QApplication.setOverrideCursor(Qt.WaitCursor)
            # noinspection PyArgumentList
            QApplication.processEvents()

            if not self.admin_layer:
                raise NoLayerProvidedException

            if not self.admin_layer and self.use_point_layer:
                raise NoLayerProvidedException

            crs_admin_layer = self.admin_layer.crs()

            # Output
            if not self.output_file_path:
                temp_file = NamedTemporaryFile(delete=False,
                                               suffix='-geopublichealth.shp')
                self.output_file_path = temp_file.name
                temp_file.flush()
                temp_file.close()
            else:
                with open(self.output_file_path, 'w') as document:
                    pass

            admin_layer_provider = self.layer.dataProvider()
            fields = admin_layer_provider.fields()

            if admin_layer_provider.fields().indexFromName(
                    self.name_field) != -1:
                raise FieldExistingException(field=self.name_field)

            fields.append(QgsField('LISA_P', QVariant.Double))
            fields.append(QgsField('LISA_Z', QVariant.Double))
            fields.append(QgsField('LISA_Q', QVariant.Int))
            fields.append(QgsField('LISA_I', QVariant.Double))
            fields.append(QgsField('LISA_C', QVariant.Double))

            # The QgsVectorFileWriter was Deprecated since 3.10 However,.......
            #The create() function DOEST NOT Flush the feature unless QGIS close.
            #options = QgsVectorFileWriter.SaveVectorOptions()
            #options.driverName = "ESRI Shapefile"
            #file_writer=QgsVectorFileWriter.create(self.output_file_path,fields,QgsWkbTypes.Polygon,self.admin_layer.crs(),QgsCoordinateTransformContext(),options)

            #It's currently a bug https://github.com/qgis/QGIS/issues/35021
            # So I will keep it for now

            file_writer = QgsVectorFileWriter(self.output_file_path, 'utf-8',
                                              fields, QgsWkbTypes.Polygon,
                                              self.admin_layer.crs(),
                                              'ESRI Shapefile')

            if self.cbx_contiguity.currentIndex() == 0:  # queen
                # fix_print_with_import

                print('Info: Local Moran\'s using queen contiguity')
                #Pysal 2.0 change
                #https://github.com/pysal/pysal/blob/master/MIGRATING.md

                w = Queen.from_shapefile(self.admin_layer.source())
            else:  # 1 for rook
                # fix_print_with_import
                print('Info: Local Moran\'s using rook contiguity')
                w = Rook.from_shapefile(self.admin_layer.source())

            #Pysal 2.0
            #https://stackoverflow.com/questions/59455383/pysal-does-not-have-attribute-open
            import geopandas

            f = geopandas.read_file(self.admin_layer.source().replace(
                '.shp', '.dbf'))

            y = f[str(field)]
            lm = Moran_Local(y, w, transformation="r", permutations=999)

            sig_q = lm.q * (lm.p_sim <= 0.05
                            )  # could make significance level an option
            outFeat = QgsFeature()
            i = 0

            count = self.admin_layer.featureCount()

            for i, feature in enumerate(self.admin_layer.getFeatures()):
                attributes = feature.attributes()
                attributes.append(float(lm.p_sim[i]))
                attributes.append(float(lm.z_sim[i]))
                attributes.append(int(lm.q[i]))
                attributes.append(float(lm.Is[i]))
                attributes.append(int(sig_q[i]))

                new_feature = QgsFeature()
                new_geom = QgsGeometry(feature.geometry())
                new_feature.setAttributes(attributes)
                new_feature.setGeometry(new_geom)
                file_writer.addFeature(new_feature)

            del file_writer

            self.output_layer = QgsVectorLayer(self.output_file_path,
                                               "LISA Moran's I - " + field,
                                               'ogr')
            QgsProject.instance().addMapLayer(self.output_layer)

            self.add_symbology()

            self.signalStatus.emit(3, tr('Successful process'))

        except GeoPublicHealthException as e:
            display_message_bar(msg=e.msg, level=e.level, duration=e.duration)

        finally:
            self.button_box_ok.setDisabled(False)
            # noinspection PyArgumentList
            QApplication.restoreOverrideCursor()
            # noinspection PyArgumentList
            QApplication.processEvents()
Пример #12
0
def neighbour_distance(objects, tessellation, unique_id, spatial_weights=None):
    """
    Calculate the mean distance to buildings on adjacent cells

    .. math::
        \\frac{1}{n}\\sum_{i=1}^n dist_i=\\frac{dist_1+dist_2+\\cdots+dist_n}{n}

    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing objects to analyse
    tessellation : GeoDataFrame
        GeoDataFrame containing morphological tessellation - source of spatial_weights.
        It is crucial to use exactly same input as was used durign the calculation of weights matrix.
        If spatial_weights is None, tessellation is used to calulate it.
    unique_id : str
        name of the column with unique id
    spatial_weights : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity matrix will be calculated
        based on tessellation

    Returns
    -------
    Series
        Series containing resulting values.

    References
    ---------
    Schirmer PM and Axhausen KW (2015) A multiscale classification of urban morphology.
    Journal of Transport and Land Use 9(1): 101–130.

    Examples
    --------
    >>> buildings_df['neighbour_distance'] = momepy.neighbour_distance(buildings_df, tessellation_df, 'uID')
    Calculating distances...
    Calculating spatial weights...
    Spatial weights ready...
    100%|██████████| 144/144 [00:00<00:00, 345.78it/s]
    Distances calculated.
    >>> buildings_df['neighbour_distance'][0]
    29.18589019096464
    """
    # define empty list for results
    results_list = []

    print('Calculating distances...')

    if not all(tessellation.index == range(len(tessellation))):
        raise ValueError(
            'Index is not consecutive range 0:x, spatial weights will not match objects.'
        )

    if spatial_weights is None:
        print('Calculating spatial weights...')
        from libpysal.weights import Queen
        spatial_weights = Queen.from_dataframe(tessellation)
        print('Spatial weights ready...')

    # iterating over rows one by one
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        uid = tessellation.loc[tessellation[unique_id] ==
                               row[unique_id]].index[0]
        neighbours = spatial_weights.neighbors[uid]

        neighbours_ids = tessellation.iloc[neighbours][unique_id]
        building_neighbours = objects.loc[objects[unique_id].isin(
            neighbours_ids)]
        if len(building_neighbours) > 0:
            results_list.append(
                np.mean(building_neighbours.geometry.distance(
                    row['geometry'])))
        else:
            results_list.append(0)

    series = pd.Series(results_list)

    print('Distances calculated.')
    return series
Пример #13
0
def greedy(
    gdf,
    strategy="balanced",
    balance="count",
    min_colors=4,
    sw="queen",
    min_distance=None,
    silence_warnings=True,
    interchange=False,
):
    """
    Color GeoDataFrame using various strategies of greedy (topological) colouring.

    Attempts to color a GeoDataFrame using as few colors as possible, where no
    neighbours can have same color as the feature itself. Offers various strategies
    ported from QGIS or implemented within networkX for greedy graph coloring.

    ``greedy`` will return pandas.Series representing assinged color codes.

    Parameters
    ----------
    gdf : GeoDataFrame
        GeoDataFrame
    strategy : str (default 'balanced')
        Determine coloring strategy. Options are ``'balanced'`` for algorithm based on
        QGIS Topological coloring. It is aiming for a visual balance, defined by the
        balance parameter.

        Other options are those supported by networkx.greedy_color:

        * ``'largest_first'``
        * ``'random_sequential'``
        * ``'smallest_last'``
        * ``'independent_set'``
        * ``'connected_sequential_bfs'``
        * ``'connected_sequential_dfs'``
        * ``'connected_sequential'`` (alias for the previous strategy)
        * ``'saturation_largest_first'``
        * ``'DSATUR'`` (alias for the previous strategy)

        For details see
        https://networkx.github.io/documentation/stable/reference/algorithms/generated/networkx.algorithms.coloring.greedy_color.html

    balance : str (default 'count')
        If strategy is ``'balanced'``, determine the method of color balancing.

        * ``'count'`` attempts to balance the number of features per each color.
        * ``'area'`` attempts to balance the area covered by each color.
        * ``'centroid'`` attempts to balance the distance between colors based on the distance between centroids.
        * ``'distance'`` attempts to balance the distance between colors based on the distance between geometries. Slower than ``'centroid'``, but more precise.

        ``'centroid'`` and ``'distance'`` are significantly slower than other especially
        for larger GeoDataFrames.

        Apart from ``'count'``, all require CRS to be projected (not in degrees) to ensure
        metric values are correct.

    min_colors: int (default 4)
        If strategy is ``'balanced'``, define the minimal number of colors to be used.

    sw : 'queen', 'rook' or libpysal.weights.W (default 'queen')
        If min_distance is None, one can pass ``'libpysal.weights.W'`` object denoting neighbors
        or let greedy to generate one based on ``'queen'`` or ``'rook'`` contiguity.

    min_distance : float
        Set minimal distance between colors.

        If min_distance is not None, slower algorithm for generating spatial weghts is used
        based on intersection between geometries. Min_distance is then used as a tolerance
        of intersection.

    silence_warnings : bool (default True)
        Silence libpysal warnings when creating spatial weights.

    interchange : bool (defaul False)
        Use the color interchange algorithm (applicable for networkx strategies)

        For details see
        https://networkx.github.io/documentation/stable/reference/algorithms/generated/networkx.algorithms.coloring.greedy_color.html

    Examples
    --------
    Default:

    >>> gdf['greedy_colors'] = greedy(gdf)

    Balanced by area:

    >>> gdf['balanced_area'] = greedy(gdf, strategy='balanced',
    >>>                               balance='area')

    Using rook adjacency:

    >>> gdf['rook_adjacency'] = greedy(gdf, sw='rook')

    Adding minimal distance between colors:

    >>> gdf['min_distance'] = greedy(gdf, min_distance=100)

    Using different coloring strategy:

    >>> gdf['smallest_last'] = greedy(gdf, strategy='smallest_last')


    Returns
    -------
    color : pd.Series
        pandas.Series representing assinged color codes
    """
    if min_distance is not None:
        sw = _geos_sw(gdf,
                      tolerance=min_distance,
                      silence_warnings=silence_warnings)

    if not isinstance(sw, W):
        if sw == "queen":
            sw = Queen.from_dataframe(gdf,
                                      ids=gdf.index.to_list(),
                                      silence_warnings=silence_warnings)
        elif sw == "rook":
            sw = Rook.from_dataframe(gdf,
                                     ids=gdf.index.to_list(),
                                     silence_warnings=silence_warnings)

    if strategy == "balanced":
        return pd.Series(
            _balanced(gdf, sw, balance=balance, min_colors=min_colors))

    elif strategy in STRATEGIES:
        color = nx.greedy_color(sw.to_networkx(),
                                strategy=strategy,
                                interchange=interchange)
        color = pd.Series(color).sort_index()
        color.index = gdf.index
        return color

    else:
        raise ValueError("{} is not a valid strategy.".format(strategy))
Пример #14
0
def neighbours(objects, spatial_weights=None, weighted=False):
    """
    Calculate the number of topological neighbours of each object.

    Topological neighbours are defined by queen adjacency. If weighted=True, number of neighbours
    will be divided by the perimeter of object, to return relative value.

    .. math::


    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing objects to analyse
    spatial_weights : libpysal.weights (default None)
        spatial weights matrix - If None, Queen contiguity matrix will be calculated
        based on tessellation
    weighted : bool (default False)
        if weighted=True, number of neighbours will be divided by the perimeter of object, to return relative value

    Returns
    -------
    Series
        Series containing resulting values.

    References
    ---------
    Hermosilla T, Ruiz LA, Recio JA, et al. (2012) Assessing contextual descriptive features
    for plot-based classification of urban areas. Landscape and Urban Planning, Elsevier B.V.
    106(1): 124–137.

    Examples
    --------
    >>> tessellation_df['neighbours'] = momepy.neighbours(tessellation_df)
    Calculating spatial weights...
    Spatial weights ready...
    Calculating neighbours...
    100%|██████████| 144/144 [00:00<00:00, 6909.50it/s]
    Neighbours calculated.
    >>> tessellation_df['neighbours'][0]
    4
    """

    if not all(objects.index == range(len(objects))):
        raise ValueError(
            'Index is not consecutive range 0:x, spatial weights will not match objects.'
        )

    # if weights matrix is not passed, generate it from objects
    if spatial_weights is None:
        print('Calculating spatial weights...')
        from libpysal.weights import Queen
        spatial_weights = Queen.from_dataframe(objects, silence_warnings=True)
        print('Spatial weights ready...')

    print('Calculating neighbours...')
    neighbours = []
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        if weighted is True:
            neighbours.append(spatial_weights.cardinalities[index] /
                              row.geometry.length)
        else:
            neighbours.append(spatial_weights.cardinalities[index])

    series = pd.Series(neighbours)
    print('Neighbours calculated.')
    return series
Пример #15
0
def _spatial_prox_profile(data, group_pop_var, total_pop_var, m=1000):
    """
    Calculation of Spatial Proximity Profile

    Parameters
    ----------

    data          : a geopandas DataFrame with a geometry column.
    
    group_pop_var : string
                    The name of variable in data that contains the population size of the group of interest
                    
    total_pop_var : string
                    The name of variable in data that contains the total population of the unit
                    
    m             : int
                    a numeric value indicating the number of thresholds to be used. Default value is 1000. 
                    A large value of m creates a smoother-looking graph and a more precise spatial proximity profile value but slows down the calculation speed.

    Attributes
    ----------

    statistic : float
                Spatial Proximity Index
                
    core_data : a geopandas DataFrame
                A geopandas DataFrame that contains the columns used to perform the estimate.

    Notes
    -----
    Based on Hong, Seong-Yun, and Yukio Sadahiro. "Measuring geographic segregation: a graph-based approach." Journal of Geographical Systems 16.2 (2014): 211-231.

    """

    if (str(type(data)) != '<class \'geopandas.geodataframe.GeoDataFrame\'>'):
        raise TypeError(
            'data is not a GeoDataFrame and, therefore, this index cannot be calculated.'
        )

    if ('geometry' not in data.columns):
        data['geometry'] = data[data._geometry_column_name]
        data = data.drop([data._geometry_column_name], axis=1)
        data = data.set_geometry('geometry')

    if (type(m) is not int):
        raise TypeError('m must be a string.')

    if (m < 2):
        raise ValueError('m must be greater than 1.')

    if ((type(group_pop_var) is not str) or (type(total_pop_var) is not str)):
        raise TypeError('group_pop_var and total_pop_var must be strings')

    if ((group_pop_var not in data.columns)
            or (total_pop_var not in data.columns)):
        raise ValueError(
            'group_pop_var and total_pop_var must be variables of data')

    data = data.rename(columns={
        group_pop_var: 'group_pop_var',
        total_pop_var: 'total_pop_var'
    })

    if any(data.total_pop_var < data.group_pop_var):
        raise ValueError(
            'Group of interest population must equal or lower than the total population of the units.'
        )

    wij = Queen.from_dataframe(data).full()[0]
    delta = manhattan_distances(wij)

    def calculate_etat(t):
        g_t_i = np.where(data.group_pop_var / data.total_pop_var >= t, True,
                         False)
        k = g_t_i.sum()
        sub_delta_ij = delta[g_t_i, :][:, g_t_i]
        den = sub_delta_ij.sum()
        eta_t = (k**2 - k) / den
        return eta_t

    grid = np.linspace(0, 1, m)
    aux = np.array(list(map(calculate_etat, grid)))
    aux[aux == inf] = 0
    aux[aux == -inf] = 0
    curve = np.nan_to_num(aux, 0)

    threshold = data.group_pop_var.sum() / data.total_pop_var.sum()
    SPP = ((threshold - ((curve[grid < threshold]).sum() / m -
                         (curve[grid >= threshold]).sum() / m)) /
           (1 - threshold))

    core_data = data[['group_pop_var', 'total_pop_var', 'geometry']]

    return SPP, grid, curve, core_data
Пример #16
0
def _test():
    import doctest
    start_suppress = np.get_printoptions()['suppress']
    np.set_printoptions(suppress=True)
    doctest.testmod()
    np.set_printoptions(suppress=start_suppress)


if __name__ == '__main__':
    _test()
    import numpy as np
    import libpysal
    from .sur_utils import sur_dictxy, sur_dictZ
    from libpysal.examples import load_example
    from libpysal.weights import Queen

    nat = load_example('Natregimes')
    db = libpysal.io.open(nat.get_path('natregimes.dbf'), 'r')
    y_var = ['HR80', 'HR90']
    x_var = [['PS80', 'UE80'], ['PS90', 'UE90']]
    w = Queen.from_shapefile(nat.get_path("natregimes.shp"))
    w.transform = 'r'
    bigy0, bigX0, bigyvars0, bigXvars0 = sur_dictxy(db, y_var, x_var)
    reg0 = SURerrorML(bigy0,bigX0,w,regimes=regimes,name_bigy=bigyvars0,name_bigX=bigXvars0,\
        name_w="natqueen",name_ds="natregimes",vm=True,nonspat_diag=True,spat_diag=True)

    #    reg0 = SURerrorGM(bigy0,bigX0,w,regimes=regimes,name_bigy=bigyvars0,name_bigX=bigXvars0,\
    #        name_w="natqueen",name_ds="natregimes",vm=False,nonspat_diag=True,spat_diag=False)

    print(reg0.summary)
Пример #17
0
def courtyards(objects, block_id, spatial_weights=None):
    """
    Calculate the number of courtyards within the joined structure.

    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing objects to analyse
    block_id : str
        name of the column where is stored block ID
    spatial_weights : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity matrix will be calculated
        based on objects. It is to denote adjacent buildings.

    Returns
    -------
    Series
        Series containing resulting values.

    Notes
    -----
    Script is not optimised at all, so it is currently extremely slow.
    """
    # define empty list for results
    results_list = []

    print('Calculating courtyards...')

    if not all(objects.index == range(len(objects))):
        raise ValueError(
            'Index is not consecutive range 0:x, spatial weights will not match objects.'
        )

    # if weights matrix is not passed, generate it from objects
    if spatial_weights is None:
        print('Calculating spatial weights...')
        from libpysal.weights import Queen
        spatial_weights = Queen.from_dataframe(objects, silence_warnings=True)
        print('Spatial weights ready...')

    # dict to store nr of courtyards for each uID
    courtyards = {}

    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        # if the id is already present in courtyards, continue (avoid repetition)
        if index in courtyards:
            continue
        else:
            to_join = [index
                       ]  # list of indices which should be joined together
            neighbours = []  # list of neighbours
            weights = spatial_weights.neighbors[
                index]  # neighbours from spatial weights
            for w in weights:
                neighbours.append(w)  # make a list from weigths

            for n in neighbours:
                while n not in to_join:  # until there is some neighbour which is not in to_join
                    to_join.append(n)
                    weights = spatial_weights.neighbors[n]
                    for w in weights:
                        neighbours.append(
                            w
                        )  # extend neighbours by neighbours of neighbours :)
            joined = objects.iloc[to_join]
            dissolved = joined.geometry.buffer(
                0.01
            ).unary_union  # buffer to avoid multipolygons where buildings touch by corners only
            try:
                interiors = len(list(dissolved.interiors))
            except (ValueError):
                print('Something happened.')
            for b in to_join:
                courtyards[b] = interiors  # fill dict with values
    # copy values from dict to gdf
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        results_list.append(courtyards[index])

    series = pd.Series(results_list)
    print('Courtyards calculated.')
    return series
Пример #18
0
def blocks(cells, streets, buildings, id_name, unique_id):
    """
    Generate blocks based on buildings, tesselation and street network

    Adds bID to buildings and tesselation.

    Parameters
    ----------
    cells : GeoDataFrame
        GeoDataFrame containing morphological tessellation
    streets : GeoDataFrame
        GeoDataFrame containing street network
    buildings : GeoDataFrame
        GeoDataFrame containing buildings
    id_name : str
        name of the unique blocks id column to be generated
    unique_id : str
        name of the column with unique id. If there is none, it could be generated by unique_id().
        This should be the same for cells and buildings, id's should match.

    Returns
    -------
    buildings, cells, blocks : tuple

    buildings : GeoDataFrame
        GeoDataFrame containing buildings with added block ID
    cells : GeoDataFrame
        GeoDataFrame containing morphological tessellation with added block ID
    blocks : GeoDataFrame
        GeoDataFrame containing generated blocks
    """

    cells_copy = cells.copy()

    print('Buffering streets...')
    street_buff = streets.copy()
    street_buff['geometry'] = streets.buffer(0.1)

    print('Generating spatial index...')
    streets_index = street_buff.sindex

    print('Difference...')
    cells_geom = cells_copy.geometry
    new_geom = []

    for ix, cell in tqdm(cells_geom.iteritems(), total=cells_geom.shape[0]):
        # find approximate matches with r-tree, then precise matches from those approximate ones
        possible_matches_index = list(streets_index.intersection(cell.bounds))
        possible_matches = street_buff.iloc[possible_matches_index]
        new_geom.append(cell.difference(possible_matches.geometry.unary_union))

    single_geom = []
    print('Defining adjacency...')
    for p in new_geom:
        if p.type == 'MultiPolygon':
            for polygon in p:
                single_geom.append(polygon)
        else:
            single_geom.append(p)

    blocks_gdf = gpd.GeoDataFrame(geometry=gpd.GeoSeries(single_geom))
    spatial_weights = Queen.from_dataframe(blocks_gdf, silence_warnings=True)

    patches = {}
    jID = 1
    for idx, row in tqdm(blocks_gdf.iterrows(), total=blocks_gdf.shape[0]):

        # if the id is already present in courtyards, continue (avoid repetition)
        if idx in patches:
            continue
        else:
            to_join = [idx]  # list of indices which should be joined together
            neighbours = []  # list of neighbours
            weights = spatial_weights.neighbors[
                idx]  # neighbours from spatial weights
            for w in weights:
                neighbours.append(w)  # make a list from weigths

            for n in neighbours:
                while n not in to_join:  # until there is some neighbour which is not in to_join
                    to_join.append(n)
                    weights = spatial_weights.neighbors[n]
                    for w in weights:
                        neighbours.append(
                            w
                        )  # extend neighbours by neighbours of neighbours :)
            for b in to_join:
                patches[b] = jID  # fill dict with values
            jID = jID + 1

    blocks_gdf['patch'] = blocks_gdf.index.map(patches)

    print('Defining street-based blocks...')
    blocks_single = blocks_gdf.dissolve(by='patch')

    blocks_single['geometry'] = blocks_single.buffer(0.1)

    print('Defining block ID...')  # street based
    blocks_single[id_name] = None
    blocks_single[id_name] = blocks_single[id_name].astype('float')
    b_id = 1
    for idx, row in tqdm(blocks_single.iterrows(),
                         total=blocks_single.shape[0]):
        blocks_single.loc[idx, id_name] = b_id
        b_id = b_id + 1

    print('Generating centroids...')
    buildings_c = buildings.copy()
    buildings_c['geometry'] = buildings_c.representative_point(
    )  # make centroids
    blocks_single.crs = buildings.crs

    print('Spatial join...')
    centroids_tempID = gpd.sjoin(buildings_c,
                                 blocks_single,
                                 how='left',
                                 op='intersects')

    tempID_to_uID = centroids_tempID[[unique_id, id_name]]

    print('Attribute join (tesselation)...')
    cells_copy = cells_copy.merge(tempID_to_uID, on=unique_id)

    print('Generating blocks...')
    blocks = cells_copy.dissolve(by=id_name)
    cells_copy = cells_copy.drop([id_name], axis=1)

    print('Multipart to singlepart...')
    blocks = multi2single(blocks)

    blocks['geometry'] = blocks.exterior

    uid = 1
    for idx, row in tqdm(blocks.iterrows(), total=blocks.shape[0]):
        blocks.loc[idx, id_name] = uid
        uid = uid + 1
        blocks.loc[idx, 'geometry'] = Polygon(row['geometry'])

    # if polygon is within another one, delete it
    sindex = blocks.sindex
    for idx, row in tqdm(blocks.iterrows(), total=blocks.shape[0]):
        possible_matches = list(sindex.intersection(row.geometry.bounds))
        possible_matches.remove(idx)
        possible = blocks.iloc[possible_matches]

        for idx2, row2 in possible.iterrows():
            if row['geometry'].within(row2['geometry']):
                blocks.loc[idx, 'delete'] = 1

    if 'delete' in blocks.columns:
        blocks = blocks.drop(list(blocks.loc[blocks['delete'] == 1].index))

    blocks_save = blocks[[id_name, 'geometry']]

    centroids_w_bl_ID2 = gpd.sjoin(buildings_c,
                                   blocks_save,
                                   how='left',
                                   op='intersects')
    bl_ID_to_uID = centroids_w_bl_ID2[[unique_id, id_name]]

    print('Attribute join (buildings)...')
    buildings = buildings.merge(bl_ID_to_uID, on=unique_id)

    print('Attribute join (tesselation)...')
    cells = cells.merge(bl_ID_to_uID, on=unique_id)

    print('Done')
    return (buildings, cells, blocks_save)
Пример #19
0
def finaliseMapping(boardMAP):
    ###Get contiguity neighbors for mainland
    MAP_W = Queen.from_dataframe(boardMAP, idVariable='HBCode')
    ##Hacky fix to link Fife and Lothian
    final_W = getForthBridge(boardMAP, MAP_W)
    return final_W
Пример #20
0
import os
import libpysal
import geopandas
from libpysal.weights import Queen, Rook, KNN
import matplotlib.pyplot as plt

sys.path.append(os.path.abspath('..'))

libpysal.examples.available()
libpysal.examples.explain('mexico')
pth = libpysal.examples.get_path("mexicojoin.shp")
gdf = geopandas.read_file(pth)
ax = gdf.plot(edgecolor='grey', facecolor='w')
ax.set_axis_off()
w_rook = Rook.from_dataframe(gdf)
f, ax = w_rook.plot(gdf,
                    ax=ax,
                    edge_kws=dict(color='r', linestyle=':', linewidth=1),
                    node_kws=dict(marker=''))
ax.set_axis_off()
gdf.head()
w_queen = Queen.from_dataframe(gdf)
plt.show()

ax = gdf.plot(edgecolor='grey', facecolor='w')
f, ax = w_queen.plot(gdf,
                     ax=ax,
                     edge_kws=dict(color='r', linestyle=':', linewidth=1),
                     node_kws=dict(marker=''))
ax.set_axis_off()
Пример #21
0
 def __init__(self):
     self.data = gpd.GeoDataFrame(Dataset('boston_housing').download(decode_geom=True))# gpd.read_file(self.filename)
     self.data.crs = {'init': 'epsg:4326'}
     self.w = Queen.from_dataframe(self.data)
Пример #22
0
 def test_Alignment(self):
     self.df_buildings["orient"] = mm.Orientation(self.df_buildings).series
     sw = Queen.from_dataframe(self.df_tessellation, ids="uID")
     self.df_buildings["align_sw"] = mm.Alignment(
         self.df_buildings, sw, "uID", self.df_buildings["orient"]).series
     assert self.df_buildings["align_sw"][0] == 18.299481296455237
Пример #23
0
 def test_NeighborDistance(self):
     sw = Queen.from_dataframe(self.df_tessellation, ids="uID")
     self.df_buildings["dist_sw"] = mm.NeighborDistance(
         self.df_buildings, sw, "uID").series
     check = 29.18589019096464
     assert self.df_buildings["dist_sw"][0] == check
def _spatial_information_theory(data,
                                group_pop_var,
                                total_pop_var,
                                w=None,
                                unit_in_local_env=True,
                                original_crs={'init': 'epsg:4326'}):
    """
    Calculation of Spatial Information Theory index

    Parameters
    ----------

    data              : a geopandas DataFrame with a geometry column.
    
    group_pop_var     : string
                        The name of variable in data that contains the population size of the group of interest
                    
    total_pop_var     : string
                        The name of variable in data that contains the total population of the unit
                    
    w                 : W
                        A PySAL weights object. If not provided, Queen contiguity matrix is used.
                        This is used to construct the local environment around each spatial unit.
    
    unit_in_local_env : boolean
                        A condition argument that states if the local environment around the unit comprises the unit itself. Default is True.
                        
    original_crs      : the original crs code given by a dict of data, but this is later be projected for the Mercator projection (EPSG = 3395).
                        This argument is also to avoid passing data without crs and, therefore, raising unusual results.
                        This index rely on the population density and we consider the area using squared kilometers. 

    Attributes
    ----------

    statistic : float
                Spatial Information Theory Index
                
    core_data : a geopandas DataFrame
                A geopandas DataFrame that contains the columns used to perform the estimate.
                
    Notes
    -----
    Based on Reardon, Sean F., and David O’Sullivan. "Measures of spatial segregation." Sociological methodology 34.1 (2004): 121-162.
    
    This measure can be extended to a society with more than two groups.

    """
    if (str(type(data)) != '<class \'geopandas.geodataframe.GeoDataFrame\'>'):
        raise TypeError(
            'data is not a GeoDataFrame and, therefore, this index cannot be calculated.'
        )

    if ((type(group_pop_var) is not str) or (type(total_pop_var) is not str)):
        raise TypeError('group_pop_var and total_pop_var must be strings')

    if ((group_pop_var not in data.columns)
            or (total_pop_var not in data.columns)):
        raise ValueError(
            'group_pop_var and total_pop_var must be variables of data')

    if ('geometry' not in data.columns):
        data['geometry'] = data[data._geometry_column_name]
        data = data.drop([data._geometry_column_name], axis=1)
        data = data.set_geometry('geometry')

    if w is None:
        w_object = Queen.from_dataframe(data)
    else:
        w_object = w

    if (not issubclass(type(w_object), libpysal.weights.W)):
        raise TypeError('w is not a PySAL weights object')

    data = data.rename(columns={
        group_pop_var: 'group_pop_var',
        total_pop_var: 'total_pop_var'
    })

    data['compl_pop_var'] = data['total_pop_var'] - data['group_pop_var']

    # In this case, M = 2 according to Reardon, Sean F., and David O’Sullivan. "Measures of spatial segregation." Sociological methodology 34.1 (2004): 121-162.
    pi_1 = data['group_pop_var'].sum() / data['total_pop_var'].sum()
    pi_2 = data['compl_pop_var'].sum() / data['total_pop_var'].sum()
    E = -1 * (pi_1 * math.log(pi_1, 2) + pi_2 * math.log(pi_2, 2))
    T = data['total_pop_var'].sum()

    # Here you reproject the data using the Mercator projection
    data.crs = original_crs
    data = data.to_crs(crs={'init': 'epsg:3395'})  # Mercator
    sqm_to_sqkm = 10**6
    data['area_sq_km'] = data.area / sqm_to_sqkm
    tau_p = data['total_pop_var'] / data['area_sq_km']

    w_matrix = w_object.full()[0]

    if unit_in_local_env:
        np.fill_diagonal(w_matrix, 1)

    # The local context of each spatial unit is given by the aggregate context (this multiplication gives the local sum of each population)
    data['local_group_pop_var'] = np.matmul(data['group_pop_var'], w_matrix)
    data['local_compl_pop_var'] = np.matmul(data['compl_pop_var'], w_matrix)
    data['local_total_pop_var'] = np.matmul(data['total_pop_var'], w_matrix)

    pi_tilde_p_1 = np.array(data['local_group_pop_var'] /
                            data['local_total_pop_var'])
    pi_tilde_p_2 = np.array(data['local_compl_pop_var'] /
                            data['local_total_pop_var'])

    E_tilde_p = -1 * (pi_tilde_p_1 * np.log(pi_tilde_p_1) / np.log(2) +
                      pi_tilde_p_2 * np.log(pi_tilde_p_2) / np.log(2))

    SIT = 1 - 1 / (T * E) * (tau_p * E_tilde_p).sum(
    )  # This is the H_Tilde according to Reardon, Sean F., and David O’Sullivan. "Measures of spatial segregation." Sociological methodology 34.1 (2004): 121-162.

    core_data = data[['group_pop_var', 'total_pop_var', 'geometry']]

    return SIT, core_data
Пример #25
0
import sys

import geopandas as gpd
from libpysal.weights import Queen

from ..greedy import greedy
import pytest


world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
sw = Queen.from_dataframe(world, ids=world.index.to_list(), silence_warnings=True)


def test_default():
    colors = greedy(world)
    assert len(colors) == len(world)
    assert set(colors) == set([0, 1, 2, 3, 4])
    assert colors.value_counts().to_list() == [36, 36, 35, 35, 35]
    assert (colors.index == world.index).all()


@pytest.mark.parametrize("pysal_geos", [None, 0])
def test_count(pysal_geos):
    colors = greedy(
        world, strategy="balanced", balance="count", min_distance=pysal_geos
    )
    assert len(colors) == len(world)
    assert set(colors) == set([0, 1, 2, 3, 4])
    assert colors.value_counts().to_list() == [36, 36, 35, 35, 35]

Пример #26
0
def alignment(objects,
              orientations,
              tessellation,
              unique_id,
              spatial_weights=None):
    """
    Calculate the mean deviation of solar orientation of objects on adjacent cells from an object

    .. math::
        \\frac{1}{n}\\sum_{i=1}^n dev_i=\\frac{dev_1+dev_2+\\cdots+dev_n}{n}

    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing objects to analyse
    orientations : str, list, np.array, pd.Series
        the name of the dataframe column, np.array, or pd.Series where is stored object orientation value
        (can be calculated using :py:func:`momepy.orientation`)
    tessellation : GeoDataFrame
        GeoDataFrame containing morphological tessellation - source of weights_matrix.
        It is crucial to use exactly same input as was used during the calculation of weights matrix.
        If weights_matrix is None, tessellation is used to calulate it.
    unique_id : str
        the name of the dataframe column with unique id shared between a cell and a building
        (must be present in both geodataframes)
    spatial_weights : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity matrix will be calculated
        based on tessellation

    Returns
    -------
    Series
        Series containing resulting values.

    Examples
    --------
    >>> buildings_df['alignment'] = momepy.alignment(buildings_df, 'bl_orient', tessellation_df, 'uID')
    Calculating alignments...
    Calculating spatial weights...
    Spatial weights ready...
    100%|██████████| 144/144 [00:01<00:00, 140.84it/s]
    Alignments calculated.
    >>> buildings_df['alignment'][0]
    18.299481296455237
    """
    # define empty list for results
    results_list = []

    if not isinstance(orientations, str):
        objects['mm_o'] = orientations
        orientations = 'mm_o'

    print('Calculating alignments...')

    if not all(tessellation.index == range(len(tessellation))):
        raise ValueError(
            'Index is not consecutive range 0:x, spatial weights will not match objects.'
        )

    if spatial_weights is None:
        print('Calculating spatial weights...')
        from libpysal.weights import Queen
        spatial_weights = Queen.from_dataframe(tessellation)
        print('Spatial weights ready...')

    # iterating over rows one by one
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        uid = tessellation.loc[tessellation[unique_id] ==
                               row[unique_id]].index[0]
        neighbours = spatial_weights.neighbors[uid]
        neighbours_ids = []

        for n in neighbours:
            uniq = tessellation.iloc[n][unique_id]
            neighbours_ids.append(uniq)

        orientation = []
        for i in neighbours_ids:
            ori = objects.loc[objects[unique_id] == i].iloc[0][orientations]
            orientation.append(ori)

        deviations = []
        for o in orientation:
            dev = abs(o - row[orientations])
            deviations.append(dev)

        if deviations:
            results_list.append(statistics.mean(deviations))
        else:
            results_list.append(0)

    series = pd.Series(results_list)

    if 'mm_o' in objects.columns:
        objects.drop(columns=['mm_o'], inplace=True)

    print('Alignments calculated.')
    return series