Skip to content

Commit

Permalink
WIP of first few boundary chapters
Browse files Browse the repository at this point in the history
  • Loading branch information
Bergam0t committed Feb 6, 2025
1 parent 821974e commit 4b6c0f6
Show file tree
Hide file tree
Showing 68 changed files with 12,596 additions and 6,876 deletions.
23 changes: 23 additions & 0 deletions boundary_allocations.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Region,Centre,Dispatcher
Telford and Wrekin,Centre 1,1
Shropshire,Centre 1,1
Cannock Chase,Centre 1,2
Lichfield,Centre 1,2
Tamworth,Centre 1,2
South Staffordshire,Centre 1,3
Walsall,Centre 1,3
Wolverhampton,Centre 1,4
Dudley,Centre 1,5
Sandwell,Centre 1,5
Birmingham,Centre 1,6
Solihull,Centre 1,7
North Warwickshire,Centre 1,7
Bromsgrove,Centre 2,1
Wyre Forest,Centre 2,2
Wychavon,Centre 2,3
Worcester,Centre 2,3
Malvern Hills,Centre 2,4
Herefordshire,Centre 2,4
Redditch,Centre 2,5
Stratford-on-Avon,Centre 2,5
Warwick,Centre 2,5
22 changes: 9 additions & 13 deletions boundary_problems_varying_evaluating_simple.qmd
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
---
title: "Boundary Problems - Creating and evaluating simple solutions"
title: "(WIP) Boundary Problems - Creating and Evaluating Simple Solutions"
format: html
---



## Generating and representing new boundaries

When we come to modifying our existing boundaries,

### Calculating the LSOAs inside our existing boundaries
When we come to modifying our existing boundaries, we are going to generate a large set of random new boundaries and test them.

:::{.callout-note}
Remember - in our problem statement, we specified that boundaries in our problem cannot cross through the middle of an LSOA. You would need to apply a different approach if this is not true in your case.
:::


:::{.callout-note}
Remember -
:::{.callout-tip}
If you have a series of pre-selected solutions to try, the code in the following sections can be adapted to use those solutions rather than the randomly-generated solutions.
:::


First, we will define the LSOAs that each LSOA has a continuous boundary with. These will form part of a possible series of solutions. To do this, we'll be using the `.touches` method in geopandas.

In this case, a solution must meet a criteria

- every polygon must be assigned to a dispatcher
- no polygon can be assigned to more than one dispatcher
-
- dispatcher boundaries must be continuous; regions belonging to a dispatcher cannot be entirely separated from the rest of their dispatch area

At this point, we are not trying to do anything to balance our objectives like equalising the number of calls they are going to; instead, we are simply coming up with possibilities. We'll actually test them out in the next chapter.

### The Process

To create a solution that scales well to any number of dispatchers, we will have the dispatchers start with a single randomly-selected patch from their existing location.

Expand All @@ -44,13 +41,12 @@ This is one of many approaches you could take.
:::

:::{.callout-note}
When we come to apply a evolutionary or genetic algorithm approach to this problem in a later chapter, we will need to change how we represent our solutions; for now, however, we
When we come to apply a evolutionary or genetic algorithm approach to this problem in a later chapter, we will need to change how we represent our solutions; for now, however, we can just put together lists of LSOAs that will belong to each dispatcher.
:::

Let's write and apply this function to generate a series of randomly generated solutions for our problem, which we will subsequently move on to evaluating.



```{python}
Expand Down
108 changes: 107 additions & 1 deletion boundary_problems_visualising_historical_boundaries.qmd
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: "Visualising our historical boundaries"
title: "(WIP) Loading and Visualising the Historical Boundaries"
format: html
---

Expand Down Expand Up @@ -113,6 +113,112 @@ This information has been given to us in a dataframe; let's import and inspect t


```{python}
import pandas as pd
boundary_allocations_df = pd.read_csv("boundary_allocations.csv")
boundary_allocations_df.head()
```

If we join this to our boundary data, with the geodataframe on the **left** of the merge, we can then access the data about the dispatchers and centers as variables we can plot.

First, let's do the join and view the resulting dataframer which will have the additional columns. As the original dataframe only contains separate columns for center and dispatcher, we will first create an additional column that joins the two together.

```{python}
bham_region = pd.merge(
bham_region,
boundary_allocations_df,
left_on="region",
right_on="Region",
how="left"
)
bham_region["centre_dispatcher"] = bham_region["Centre"].astype("str") + '-' + bham_region["Dispatcher"].astype("str")
bham_region.head()
```

Let's first just visualise the split across our dispatching centres by plotting the center column.

```{python}
ax = bham_region.plot(
edgecolor='black',
column="Centre",
legend=True,
legend_kwds={'loc':'center left', 'bbox_to_anchor':(1.2, 0.5)}
)
ax.axis('off')
```

Then we'll visualise the split per dispatcher, using our new column to avoid issues with dispatcher numbers being duplicated across centres.

```{python}
ax = bham_region.plot(
edgecolor='black',
column="centre_dispatcher",
legend=True,
cmap="tab20",
legend_kwds={'loc':'center left', 'bbox_to_anchor':(1.2, 0.5)}
)
ax.axis('off')
```

Finally, let's demonstrate how we could overlay the plot by dispatcher with something that highlights the per-centre boundary as well.

To do this, we will have to merge all of the polygons into a single large polygon. This will give us a brand new dataframe with just two rows referencing the two large polygons; we will view this at the end.

*GenAI Alert - This code was modified from a suggested approach provided by ChatGPT*

```{python}
# Group by the specified column
grouped_centre_gdf = bham_region.groupby('Centre')
# Create a new GeoDataFrame for the boundaries of each group
boundary_list = []
for group_name, group in grouped_centre_gdf:
# Combine the polygons in each group into one geometry
combined_geometry = group.unary_union
# Get the boundary of the combined geometry
boundary = combined_geometry.boundary
# Add the boundary geometry and the group name to the list
boundary_list.append({'group': group_name, 'boundary': boundary})
# Create a GeoDataFrame from the list of boundaries
grouped_centre_gdf_boundary = geopandas.GeoDataFrame(boundary_list, geometry='boundary', crs=bham_region.crs)
grouped_centre_gdf_boundary.head()
```

Finally, we'll repeat our per-dispatcher

```{python}
ax = bham_region.plot(
edgecolor='black',
column="centre_dispatcher",
legend=True,
cmap="tab20",
legend_kwds={'loc':'center left', 'bbox_to_anchor':(1.2, 0.5)}
)
grouped_centre_gdf_boundary.plot(
edgecolor='red',
ax=ax,
linewidth=2
)
ax.axis('off')
```


## Bringing in historical demand data

Finally, let's bring in some historical demand data to see if we can spot any obvious issues.

*Placeholder - we will have per-LSOA demand visualised here; a dummy dataset modified of population modified by some random factor. Around it, we will show how we could draw both the centre and dispatcher boundaries.
1 change: 0 additions & 1 deletion docs/CNAME

This file was deleted.

Loading

0 comments on commit 4b6c0f6

Please sign in to comment.