From 5b910d6a523449b2b05c276f5f859cf814add961 Mon Sep 17 00:00:00 2001 From: d3netxer Date: Mon, 28 Dec 2020 21:52:45 -0500 Subject: [PATCH 1/4] preserving node id types from original graph --- peartree/graph.py | 4 ++++ peartree/paths.py | 3 +++ peartree/toolkit.py | 7 ++++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/peartree/graph.py b/peartree/graph.py index afeb573..2b0dfb4 100644 --- a/peartree/graph.py +++ b/peartree/graph.py @@ -188,6 +188,7 @@ def generate_cross_feed_edges(G: nx.MultiDiGraph, lat = float(row.stop_lat) lon = float(row.stop_lon) point = (lat, lon) + nearest_nodes = get_nearest_nodes(node_df, point, connection_threshold, @@ -247,6 +248,7 @@ def _add_cross_feed_edges(G: nx.MultiDiGraph, walk_speed_kmph: float) -> nx.MultiDiGraph: # Add the cross feed edge connectors to the graph to # capture transfer points + for i, row in cross_feed_edges.iterrows(): # Extract the row column values as discrete variables sid = row.stop_id @@ -351,9 +353,11 @@ def populate_graph(G: nx.MultiDiGraph, stops_df = _merge_stop_waits_and_attributes( wait_times_and_modes, feed.stops) + # Mutates the G network object sid_lookup = _add_nodes_and_edges(G, name, stops_df, summary_edge_costs) + # Default to exempt new edges created, unless imputing internal # walk transfers is requested as well exempt_nodes = sid_lookup.values() diff --git a/peartree/paths.py b/peartree/paths.py index 0cbd7a9..7ca7db1 100644 --- a/peartree/paths.py +++ b/peartree/paths.py @@ -219,6 +219,9 @@ def load_feed_as_graph(feed: ptg.gtfs.Feed, stop_cost_method, use_multiprocessing) + #print("print summary_edge_costs") + #print(summary_edge_costs) + # This is a flag used to check if we need to run any additional steps # after the feed is returned to ensure that new nodes and edge can connect # with existing ones (if they exist/a graph is passed in) diff --git a/peartree/toolkit.py b/peartree/toolkit.py index a9dddcd..8a990bb 100644 --- a/peartree/toolkit.py +++ b/peartree/toolkit.py @@ -85,11 +85,12 @@ def generate_graph_node_dataframe(G): x = float(data['x']) y = float(data['y']) clist.append([node, x, y]) - coords = np.array(clist) + #coords = np.array(clist) + coords = np.array(clist, dtype='O') # Then make into a Pandas DataFrame, with the node as index (type string) df = pd.DataFrame(coords, columns=['node', 'x', 'y']) - df['node'] = df['node'].astype(str) + #df['node'] = df['node'].astype(str) df = df.set_index('node') return df @@ -107,7 +108,7 @@ def get_nearest_nodes(df_orig: pd.DataFrame, df = df_orig.copy() if exempt_id is not None: - df.index = df.index.astype(str) + #df.index = df.index.astype(str) mask = ~(df.index == exempt_id) df = df[mask] From 6429a8866551b3df2f06562aad7a255f92ef628d Mon Sep 17 00:00:00 2001 From: d3netxer Date: Wed, 6 Jan 2021 02:33:31 -0500 Subject: [PATCH 2/4] adding to and from links for connector links --- peartree/graph.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/peartree/graph.py b/peartree/graph.py index 2b0dfb4..10e4cdb 100644 --- a/peartree/graph.py +++ b/peartree/graph.py @@ -200,6 +200,7 @@ def generate_cross_feed_edges(G: nx.MultiDiGraph, to_nodes.append(node_id) edge_costs.append(dist_val) + return pd.DataFrame({'stop_id': stop_ids, 'to_node': to_nodes, 'distance': edge_costs}) @@ -266,6 +267,8 @@ def _add_cross_feed_edges(G: nx.MultiDiGraph, # And then add it to the graph G.add_edge(full_sid, to, length=in_seconds, mode='walk') + # also add reverse edge + G.add_edge(to, full_sid, length=in_seconds, mode='walk') def _add_nodes_and_edges(G: nx.MultiDiGraph, From b7075ecb19006b38817d6ec9aa8cf5e6b21d15e1 Mon Sep 17 00:00:00 2001 From: d3netxer Date: Sat, 16 Jan 2021 02:17:03 -0500 Subject: [PATCH 3/4] adding option to add attribute to graph edges that represents the number of trips that pass through an edge --- peartree/graph.py | 24 +++++++++++++++++++----- peartree/paths.py | 8 ++++++-- peartree/summarizer.py | 10 +++++++++- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/peartree/graph.py b/peartree/graph.py index 10e4cdb..c372564 100644 --- a/peartree/graph.py +++ b/peartree/graph.py @@ -275,10 +275,14 @@ def _add_nodes_and_edges(G: nx.MultiDiGraph, name: str, stops_df: pd.DataFrame, summary_edge_costs: pd.DataFrame, - bidirectional: bool=False) -> Dict[str, str]: + bidirectional: bool=False, + add_trips_per_edge: bool=False) -> Dict[str, str]: # As we convert stop ids to actual nodes, let's keep track of those names # here so that we can reference them when we add connector edges across # the various feeds loaded into the graph + print("print add_trips_per_edge val from graph") + print(add_trips_per_edge) + sid_lookup = {} for i, row in stops_df.iterrows(): @@ -296,12 +300,18 @@ def _add_nodes_and_edges(G: nx.MultiDiGraph, for i, row in summary_edge_costs.iterrows(): sid_fr = nameify_stop_id(name, row.from_stop_id) sid_to = nameify_stop_id(name, row.to_stop_id) - G.add_edge(sid_fr, sid_to, length=row.edge_cost, mode='transit') + if add_trips_per_edge: + G.add_edge(sid_fr, sid_to, length=row.edge_cost, trips=row.trips_per_edge, mode='transit') + else: + G.add_edge(sid_fr, sid_to, length=row.edge_cost, mode='transit') # If want to add both directions in this step, we can # by reversing the to/from node order on edge if bidirectional: - G.add_edge(sid_to, sid_fr, length=row.edge_cost, mode='transit') + if add_trips_per_edge: + G.add_edge(sid_to, sid_fr, length=row.edge_cost, trips=row.trips_per_edge, mode='transit') + else: + G.add_edge(sid_to, sid_fr, length=row.edge_cost, mode='transit') return sid_lookup @@ -313,7 +323,8 @@ def populate_graph(G: nx.MultiDiGraph, summary_edge_costs: pd.DataFrame, connection_threshold: Union[int, float], walk_speed_kmph: float=4.5, - impute_walk_transfers: bool=True) -> nx.MultiDiGraph: + impute_walk_transfers: bool=True, + add_trips_per_edge: bool=False) -> nx.MultiDiGraph: """ Takes an existing network graph object and adds all new nodes, transit \ edges, and connector edges between existing and new nodes (stop ids). @@ -347,6 +358,9 @@ def populate_graph(G: nx.MultiDiGraph, G : nx.MultiDiGraph The muti-directed graph """ + print("print add_trips_per_edge val from populate_graph") + print(add_trips_per_edge) + wait_times_and_modes = _add_modes_to_wait_times(feed, wait_times_by_stop) # Generate a merge of the wait time data and the feed stops data that will @@ -358,7 +372,7 @@ def populate_graph(G: nx.MultiDiGraph, # Mutates the G network object - sid_lookup = _add_nodes_and_edges(G, name, stops_df, summary_edge_costs) + sid_lookup = _add_nodes_and_edges(G, name, stops_df, summary_edge_costs, False, add_trips_per_edge) # Default to exempt new edges created, unless imputing internal diff --git a/peartree/paths.py b/peartree/paths.py index 7ca7db1..4ac8953 100644 --- a/peartree/paths.py +++ b/peartree/paths.py @@ -139,7 +139,8 @@ def load_feed_as_graph(feed: ptg.gtfs.Feed, fallback_stop_cost: bool=FALLBACK_STOP_COST_DEFAULT, interpolate_times: bool=True, impute_walk_transfers: bool=False, - use_multiprocessing: bool=False): + use_multiprocessing: bool=False, + add_trips_per_edge: bool=False): """ Convert a feed object into a NetworkX Graph, or connect to an existing \ NetworkX graph if one is supplied. @@ -197,6 +198,8 @@ def load_feed_as_graph(feed: ptg.gtfs.Feed, networkx.Graph, the loaded, combined representation of the schedule \ data from the feed subset by the time parameters provided """ + print("print add_trips_per_edge val") + print(add_trips_per_edge) # Generate a random name for name if it is None if not name: @@ -243,7 +246,8 @@ def load_feed_as_graph(feed: ptg.gtfs.Feed, summary_edge_costs, connection_threshold, walk_speed_kmph, - impute_walk_transfers) + impute_walk_transfers, + add_trips_per_edge) def load_synthetic_network_as_graph( diff --git a/peartree/summarizer.py b/peartree/summarizer.py index c10246c..33aa6e0 100644 --- a/peartree/summarizer.py +++ b/peartree/summarizer.py @@ -60,13 +60,20 @@ def summarize_edge_costs( # operator out from the nested summarize_edge_costs method avg_cost = unique_edge_operator(df[to_mask].edge_cost) + # number of trips that travel on edge + trips_per_edge = len(df[to_mask]) + # Add each row-array to the reference matrix, which... results_mtx.append([avg_cost, + trips_per_edge, from_stop_id, to_stop_id]) + # add a new column + new_columns = ['edge_cost', 'trips_per_edge', 'from_stop_id', 'to_stop_id'] + # ...will be converted into a DataFrame upon being returned - return pd.DataFrame(results_mtx, columns=df.columns) + return pd.DataFrame(results_mtx, columns=new_columns) def generate_summary_edge_costs(all_edge_costs: pd.DataFrame) -> pd.DataFrame: @@ -85,6 +92,7 @@ def generate_summary_edge_costs(all_edge_costs: pd.DataFrame) -> pd.DataFrame: Edge costs grouped by from-to node pairs. """ summary_groupings = all_edge_costs.groupby('from_stop_id') + summary = summary_groupings.apply(summarize_edge_costs) summary = summary.reset_index(drop=True) return summary From 577b077c169c7f102d5947b5f9f273fc965eb41f Mon Sep 17 00:00:00 2001 From: d3netxer Date: Sat, 16 Jan 2021 22:55:55 -0500 Subject: [PATCH 4/4] eliminating test print statements and changing a pandas query --- peartree/graph.py | 5 ----- peartree/parallel.py | 10 ++++++---- peartree/paths.py | 3 --- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/peartree/graph.py b/peartree/graph.py index c372564..23d61f5 100644 --- a/peartree/graph.py +++ b/peartree/graph.py @@ -280,8 +280,6 @@ def _add_nodes_and_edges(G: nx.MultiDiGraph, # As we convert stop ids to actual nodes, let's keep track of those names # here so that we can reference them when we add connector edges across # the various feeds loaded into the graph - print("print add_trips_per_edge val from graph") - print(add_trips_per_edge) sid_lookup = {} @@ -358,9 +356,6 @@ def populate_graph(G: nx.MultiDiGraph, G : nx.MultiDiGraph The muti-directed graph """ - print("print add_trips_per_edge val from populate_graph") - print(add_trips_per_edge) - wait_times_and_modes = _add_modes_to_wait_times(feed, wait_times_by_stop) # Generate a merge of the wait time data and the feed stops data that will diff --git a/peartree/parallel.py b/peartree/parallel.py index 2579400..7ff1078 100644 --- a/peartree/parallel.py +++ b/peartree/parallel.py @@ -116,15 +116,17 @@ def generate_wait_times( stop_cost_method: Any) -> Dict[int, List[float]]: wait_times = {0: {}, 1: {}} for stop_id in trips_and_stop_times.stop_id.unique(): - constraint_1 = (trips_and_stop_times.stop_id == stop_id) - stop_times_by_stop = trips_and_stop_times[constraint_1] + #constraint_1 = (trips_and_stop_times.stop_id == stop_id) + #stop_times_by_stop = trips_and_stop_times[constraint_1] + stop_times_by_stop = trips_and_stop_times[trips_and_stop_times.stop_id == stop_id] # Handle both inbound and outbound directions for direction in [0, 1]: # Check if direction_id exists in source data if 'direction_id' in trips_and_stop_times: - constraint_2 = (trips_and_stop_times.direction_id == direction) - direction_subset = stop_times_by_stop[constraint_2] + #constraint_2 = (trips_and_stop_times.direction_id == direction) + #direction_subset = stop_times_by_stop[constraint_2] + direction_subset = trips_and_stop_times[(trips_and_stop_times.stop_id == stop_id) & (trips_and_stop_times.direction_id == direction)] else: direction_subset = stop_times_by_stop.copy() diff --git a/peartree/paths.py b/peartree/paths.py index 4ac8953..f744ddc 100644 --- a/peartree/paths.py +++ b/peartree/paths.py @@ -198,9 +198,6 @@ def load_feed_as_graph(feed: ptg.gtfs.Feed, networkx.Graph, the loaded, combined representation of the schedule \ data from the feed subset by the time parameters provided """ - print("print add_trips_per_edge val") - print(add_trips_per_edge) - # Generate a random name for name if it is None if not name: name = generate_random_name()