def test_weighted_character_array(self): area = self.df_buildings.geometry.area sw = Queen_higher(k=3, geodataframe=self.df_tessellation) weighted = mm.weighted_character(self.df_buildings, self.df_tessellation, 'height', 'uID', sw, area) assert weighted[38] == 18.301521351817303
def test_mean_character(self): self.df_tessellation['mesh'] = mm.mean_character(self.df_tessellation) spatial_weights = Queen_higher(k=3, geodataframe=self.df_tessellation) self.df_tessellation['mesh_sw'] = mm.mean_character( self.df_tessellation, spatial_weights) self.df_tessellation[ 'area'] = area = self.df_tessellation.geometry.area self.df_tessellation['mesh_ar'] = mm.mean_character( self.df_tessellation, spatial_weights, values='area') self.df_tessellation['mesh_array'] = mm.mean_character( self.df_tessellation, spatial_weights, values=area) self.df_tessellation['mesh_id'] = mm.mean_character( self.df_tessellation, spatial_weights, values='area', rng=(10, 90)) self.df_tessellation['mesh_iq'] = mm.mean_character( self.df_tessellation, spatial_weights, values='area', rng=(25, 75)) neighbours = spatial_weights.neighbors[38] total_area = sum(self.df_tessellation.iloc[neighbours].geometry.area ) + self.df_tessellation.geometry.area[38] check = total_area / (len(neighbours) + 1) assert self.df_tessellation['mesh'][38] == check assert self.df_tessellation['mesh_sw'][38] == check assert self.df_tessellation['mesh_ar'][38] == check assert self.df_tessellation['mesh_array'][38] == check assert self.df_tessellation['mesh_id'][38] == 2206.611646069303 assert self.df_tessellation['mesh_iq'][38] == 2118.609142733066 gdf = self.df_tessellation gdf.index = gdf.index + 20 with pytest.raises(ValueError): self.df_tessellation['meshE'] = mm.mean_character(gdf)
def test_theil(self): full = mm.theil(self.df_tessellation, 'area', order=3) assert full[0] == 0.2574468431886534 sw = Queen_higher(k=3, geodataframe=self.df_tessellation) full_sw = mm.theil(self.df_tessellation, 'area', spatial_weights=sw) assert full_sw[0] == 0.2574468431886534 limit = mm.theil(self.df_tessellation, 'area', spatial_weights=sw, rng=(10, 90)) assert limit[0] == 0.13302952097969373
def test_simpson(self): ht = mm.simpson(self.df_tessellation, 'area', order=3) assert ht[0] == 0.385 sw = Queen_higher(k=3, geodataframe=self.df_tessellation) ht_sw = mm.simpson(self.df_tessellation, 'area', spatial_weights=sw) assert ht_sw[0] == 0.385 quan_sw = mm.simpson(self.df_tessellation, 'area', spatial_weights=sw, binning='quantiles', k=3) assert quan_sw[0] == 0.395
def test_rng(self): full = mm.rng(self.df_tessellation, 'area', order=3) assert full[0] == 8255.372874447059 sw = Queen_higher(k=3, geodataframe=self.df_tessellation) full_sw = mm.rng(self.df_tessellation, 'area', spatial_weights=sw) assert full_sw[0] == 8255.372874447059 area = self.df_tessellation['area'] full2 = mm.rng(self.df_tessellation, area, order=3) assert full2[0] == 8255.372874447059 limit = mm.rng(self.df_tessellation, 'area', spatial_weights=sw, rng=(10, 90)) assert limit[0] == 4122.139212736442
def test_wall(self): sw = Queen_higher(geodataframe=self.df_buildings, k=1) wall = mm.wall(self.df_buildings) wall_sw = mm.wall(self.df_buildings, sw) assert wall[0] == wall_sw[0] assert wall[0] == 137.2106961418436
def test_covered_area(self): sw = Queen_higher(geodataframe=self.df_tessellation, k=1) covered = mm.covered_area(self.df_tessellation) covered_sw = mm.covered_area(self.df_tessellation, sw) assert covered[0] == covered_sw[0] assert covered[0] == 24115.66721833942
def test_weighted_character_sw(self): sw = Queen_higher(k=3, geodataframe=self.df_tessellation) weighted = mm.weighted_character(self.df_buildings, self.df_tessellation, 'height', 'uID', sw) assert weighted[38] == 18.301521351817303
def simpson(objects, values, spatial_weights=None, order=None, binning='HeadTail_Breaks', **classification_kwds): """ Calculates the Simpson\'s diversity index of values within k steps of morphological tessellation Uses `mapclassify.classifiers` under the hood for binning. .. math:: Parameters ---------- objects : GeoDataFrame GeoDataFrame containing morphological tessellation values : str, list, np.array, pd.Series the name of the dataframe column, np.array, or pd.Series where is stored character value. spatial_weights : libpysal.weights, optional spatial weights matrix - If None, Queen contiguity matrix of set order will be calculated based on objects. order : int order of Queen contiguity binning : str One of mapclassify classification schemes Options are Box_Plot, Equal_Interval, Fisher_Jenks, Fisher_Jenks_Sampled, HeadTail_Breaks, Jenks_Caspall, Jenks_Caspall_Forced, Jenks_Caspall_Sampled, Max_P_Classifier, Maximum_Breaks, Natural_Breaks, Quantiles, Percentiles, Std_Mean, User_Defined **classification_kwds : dict Keyword arguments for classification scheme For details see mapclassify documentation: https://mapclassify.readthedocs.io/en/latest/api.html Returns ------- Series Series containing resulting values. References ---------- Examples -------- """ def simpson_di(data): """ Given a hash { 'species': count } , returns the Simpson Diversity Index >>> simpson_di({'a': 10, 'b': 20, 'c': 30,}) 0.3888888888888889 https://gist.github.com/martinjc/f227b447791df8c90568 """ def p(n, N): """ Relative abundance """ if n == 0: return 0 else: return float(n) / N N = sum(data.values()) return sum(p(n, N)**2 for n in data.values() if n != 0) try: import mapclassify.classifiers except ImportError: raise ImportError( "The 'mapclassify' package is required to use the 'scheme' " "keyword") schemes = {} for classifier in mapclassify.classifiers.CLASSIFIERS: schemes[classifier.lower()] = getattr(mapclassify.classifiers, classifier) binning = binning.lower() if binning not in schemes: raise ValueError("Invalid binning. Binning must be in the" " set: %r" % schemes.keys()) # define empty list for results results_list = [] print('Calculating Simpson\'s diversity index...') if spatial_weights 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 = Queen_higher(k=order, geodataframe=objects) else: if not all(objects.index == range(len(objects))): raise ValueError('Index is not consecutive range 0:x, spatial weights will not match objects.') if values is not None: if not isinstance(values, str): objects['mm_v'] = values values = 'mm_v' bins = schemes[binning](objects[values], **classification_kwds).bins for index, row in tqdm(objects.iterrows(), total=objects.shape[0]): neighbours = spatial_weights.neighbors[index] values_list = objects.iloc[neighbours][values].tolist() if values_list: values_list.append(row[values]) else: values_list = [row[values]] sample_bins = mapclassify.classifiers.User_Defined(values_list, bins) counts = dict(zip(bins, sample_bins.counts)) results_list.append(simpson_di(counts)) series = pd.Series(results_list) if 'mm_v' in objects.columns: objects.drop(columns=['mm_v'], inplace=True) print('Simpson\'s diversity index calculated.') return series
def theil(objects, values, spatial_weights=None, order=None, rng=(0, 100)): """ Calculates the Theil measure of inequality of values within k steps of morphological tessellation Uses `pysal.explore.inequality.theil.Theil` under the hood. .. math:: Parameters ---------- objects : GeoDataFrame GeoDataFrame containing morphological tessellation values : str, list, np.array, pd.Series the name of the dataframe column, np.array, or pd.Series where is stored character value. spatial_weights : libpysal.weights, optional spatial weights matrix - If None, Queen contiguity matrix of set order will be calculated based on objects. order : int order of Queen contiguity rng : Two-element sequence containing floats in range of [0,100], optional Percentiles over which to compute the range. Each must be between 0 and 100, inclusive. The order of the elements is not important. Returns ------- Series Series containing resulting values. References ---------- Examples -------- """ from pysal.explore.inequality.theil import Theil # define empty list for results results_list = [] print('Calculating Theil index...') if spatial_weights 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 = Queen_higher(k=order, geodataframe=objects) else: if not all(objects.index == range(len(objects))): raise ValueError('Index is not consecutive range 0:x, spatial weights will not match objects.') if values is not None: if not isinstance(values, str): objects['mm_v'] = values values = 'mm_v' for index, row in tqdm(objects.iterrows(), total=objects.shape[0]): neighbours = spatial_weights.neighbors[index] values_list = objects.iloc[neighbours][values].tolist() if values_list: values_list.append(row[values]) else: values_list = [row[values]] if rng: from momepy import limit_range values_list = limit_range(values_list, rng=rng) results_list.append(Theil(values_list).T) series = pd.Series(results_list) if 'mm_v' in objects.columns: objects.drop(columns=['mm_v'], inplace=True) print('Theil index calculated.') return series
def node_density(objects, nodes, spatial_weights=None, order=9, node_id='nodeID'): """ Calculate the density of nodes within topological steps of morphological tessellation. Node is marked as reached if reached cell con .. math:: Parameters ---------- objects : GeoDataFrame GeoDataFrame containing tessellation objects to analyse nodes : GeoDataFrame GeoDataFrame containing nodes spatial_weights : libpysal.weights, optional spatial weights matrix - If None, Queen contiguity matrix of selected order will be calculated based on objects. order : int order of Queen contiguity node_id : str name of the column of objects gdf with node id. Returns ------- Series Series containing resulting values. References --------- Jacob Notes ----- """ # define empty list for results results_list = [] print('Calculating gross density...') if not all(objects.index == range(len(objects))): raise ValueError( 'Index is not consecutive range 0:x, spatial weights will not match objects.' ) if spatial_weights 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 = Queen_higher(k=order, geodataframe=objects) # iterating over rows one by one for index, row in tqdm(objects.iterrows(), total=objects.shape[0]): neighbours = spatial_weights.neighbors[index] neighbours.append(index) sub = objects.iloc[neighbours] subnodes = set(sub['nodeID']) results_list.append((len(subnodes) / sum(sub.geometry.area)) * 10000) series = pd.Series(results_list) print('Gross density calculated.') return series
def blocks_count(tessellation, block_id, spatial_weights=None, order=5): """ Calculates the weighted number of blocks Number of blocks within `k` topological steps defined in spatial_weights weighted by the analysed area. .. math:: \\frac{\\sum_{i=1}^{n} {blocks}}{\\sum_{i=1}^{n} area_{i}} NOT SURE Parameters ---------- tessellation : GeoDataFrame GeoDataFrame containing morphological tessellation block_id : str, list, np.array, pd.Series (default None) the name of the objects dataframe column, np.array, or pd.Series where is stored block ID. spatial_weights : libpysal.weights (default None) spatial weights matrix - If None, Queen contiguity matrix of set order will be calculated based on objects. order : int (default 5) order of Queen contiguity. Used only when spatial_weights=None. Returns ------- Series Series containing resulting values. References ---------- Jacob Examples -------- Notes ----- Blocks count or blocks density? """ # define empty list for results results_list = [] if not isinstance(block_id, str): block_id['mm_bid'] = block_id block_id = 'mm_bid' 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('Generating weights matrix (Queen) of {} topological steps...'. format(order)) from momepy import Queen_higher # matrix to define area of analysis (more steps) spatial_weights = Queen_higher(k=order, geodataframe=tessellation) print('Calculating blocks...') for index, row in tqdm(tessellation.iterrows(), total=tessellation.shape[0]): neighbours = spatial_weights.neighbors[index] neighbours.append(index) vicinity = tessellation.iloc[neighbours] results_list.append( len(set(list(vicinity[block_id]))) / sum(vicinity.geometry.area)) series = pd.Series(results_list) if 'mm_bid' in tessellation.columns: tessellation.drop(columns=['mm_bid'], inplace=True) print('Blocks calculated.') return series
def gross_density(objects, buildings, area, character, spatial_weights=None, order=3, unique_id='uID'): """ Calculate the density .. math:: Parameters ---------- objects : GeoDataFrame GeoDataFrame containing tessellation objects to analyse buildings : GeoDataFrame GeoDataFrame containing buildings area : str name of the column with area values character : str name of the column with values of target character for density calculation spatial_weights : libpysal.weights, optional spatial weights matrix - If None, Queen contiguity matrix of selected order will be calculated based on objects. order : int order of Queen contiguity unique_id : str name of the column with unique id. If there is none, it could be generated by unique_id() Returns ------- Series Series containing resulting values. References --------- Jacob?? Notes ----- Rework, it is a mess. Terminology! """ # define empty list for results results_list = [] print('Calculating gross density...') if not all(objects.index == range(len(objects))): raise ValueError( 'Index is not consecutive range 0:x, spatial weights will not match objects.' ) if spatial_weights 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 = Queen_higher(k=order, geodataframe=objects) # iterating over rows one by one for index, row in tqdm(objects.iterrows(), total=objects.shape[0]): neighbours_id = spatial_weights.neighbors[index] neighbours_id.append(index) neighbours = objects.iloc[neighbours_id] fa = buildings.loc[buildings[unique_id].isin( neighbours[unique_id])][character] results_list.append(sum(fa) / sum(neighbours[area])) series = pd.Series(results_list) print('Gross density calculated.') return series
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
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