pycart.cartogram.Cartogram

__init__(gdf, value_field, id_field, geometry_field='geometry')

A Cartogram object acts as a generator on a specific dataset, through which generation algorithms can be run.

Parameters
  • gdf : geopandas.GeoDataFrame - The dataset that you want to apply cartogram techniques to
  • value_field : String - Field in the dataset to apply cartogram techniques to; the values themselves should be integer or float values
  • id_field : String - Field of the identifier for each region in the dataset; the id values should be unique so that the generated cartograms can be linked back to original data
  • geometry_field : String, optional, default 'geometry' - Field containing the geometries of each region
Example
from pycart import cartogram
import geopandas as gpd

my_geodf = gpd.read_file("path/to/dataset.csv")

cart = cartogram.Cartogram(my_geodf, value_field='Population', id_field='Name', geo_field='Geometry')

non_contiguous(size_value=1.0)

Calculates and returns a Non-Contiguous cartogram in the form of a GeoDataFrame.

A Non-Contiguous cartogram [1] is created by scaling each region in-place by a specific density (value field divided by geographical area), about an anchor region. The anchor region is usually the region with the highest density.

Suppose we have an anchor unit $H$ of area $A_H$ and value $V_H$ and a miscellaneous region $J$, with area $A_J$ and value $V_J$. The scaling value applied to $J$ is:

$(\sqrt{V_H / A_H})^{-1} \cdot \sqrt{V_J / A_J}$

Parameters
  • size_value : int, optional, default 1.0 - A simple multiplier to scaling; larger values accentuate scaling.
Returns
Example
from pycart import cartogram
import matplotlib.pyplot as plt
import geopandas as gpd

# Load dataset into Cartogram generator
my_geodf = gpd.read_file("path/to/dataset.csv")
cart = cartogram.Cartogram(my_geodf, value_field='Population', id_field='Name', geo_field='Geometry')

# Create Non-Contiguous cartogram
non_con = cart.non_contiguous(size_value=1.0)

# Plot data
fig, ax = plt.subplots(1, figsize=(4, 4))
ax.axis('equal')
ax.axis('off')

my_geodf.plot(color='w', ax=ax, alpha=0.8, zorder=0,  edgecolor='0', linewidth=0.1, legend=False)
non_con.plot(color='r', ax=ax, edgecolor='0', linewidth=0.1, legend=False)

plt.show()

dorling(iterations=100, ratio=0.4, friction=0.5, stop=None)

Runs the Dorling cartogram algorithm and returns the generated cartogram in the form of a GeoDataFrame.

A Dorling cartogram [2] represents each region as a circle, with the radius being proportional to the density of the given region. The regions are then moved via a gravity-like force model.

The radius of a given region $J$ is calculated over multiple steps, with Steps 1 and 2 being carried out by border_util.get_borders():

  1. Use Queen contiguity to find all neighbours of a region $J$; Queen contiguity acts similarly to a Moore neighbourhood, where a neighbour is a region that shares an edge or a vertex.
  2. Assign weights to each neighbour of $J$ based on the length of perimeter that the neighbour occupies.
  3. Calculate the sum $D$, of the pairwise distance (see _paired_distance) from a region to all of its neighbours, for all regions.
  4. Calculate the sum $R$, of radii as: $R = \sum (\sqrt{V_M / \pi} + \sqrt{V_N / \pi})$ for all $N$ neighbours and for all $M$ regions

  5. Calculate the radius scaling coefficient $k$, as: $k = D / R$

  6. Calculate the radius of region $J$: $r_J = k \sqrt{V_J / \pi}$

See the helper functions _attract() and _repel() for more details on how the forces are calculated.

Parameters
  • iterations : int, default 100 - The number of iterations to run the Dorling algorithm for.
  • ratio : float, optional, default 0.4 - Ratio of attractive force to repulsive force; the larger the ratio, the more attractive force is applied.
  • friction : float, optional, default 0.25 - Determines the strength of $x$ and $y$ vectors; acts as a simple multiplier to $x$ and $y$ vectors.
  • stop : int, optional, default None - A given iteration at which to halt computation and return the cartogram
Returns
Example
from pycart import cartogram
import matplotlib.pyplot as plt
import geopandas as gpd

# Load dataset into Cartogram generator
my_geodf = gpd.read_file("path/to/dataset.csv")
cart = cartogram.Cartogram(my_geodf, value_field='Population', id_field='Name', geo_field='Geometry')

# Run Dorling algorithm for 100 iterations
dorling = cart.dorling(iterations=100, stop=None)

# Plot data
fig, ax = plt.subplots(1, figsize=(4, 4))
ax.axis('equal')
ax.axis('off')

my_geodf.plot(color='w', ax=ax, alpha=0.8, zorder=0,  edgecolor='0', linewidth=0.1, legend=False)
dorling.plot(color='w', ax=ax, alpha=0.8, zorder=0, edgecolor='0', linewidth=0.1, legend=False)

plt.show()

Helpers

_paired_distance(X, Y)

Calculates the pairwise Euclidean distance between two array_like of shapely.Point, using shapely.distance().

Both X and Y should be the same length.

Parameters

  • X, Y : array_like of shapely.Point - The two arrays to calculate distance between.

Returns

  • numpy.ndarray - array of float distances

_repel(neighbour, focal, xrepel, yrepel)

Calculates and updates the repulsive force being applied onto a given region, from a given neighbour of said region.

The repulsive force $F$ is split into $x$ and $y$ components, $F_x$ and $F_y$ respectively. These forces are calculated as the following, where $O$ is the amount the neighbour overlaps with the focal region, $D_N$ is the distance from the neighbour to the focal region, $N_x$ is the $x$ position of the neighbour and $M_x$ is the $x$ position of the focal region:

$F_x = O \cdot (N_x - M_x) / D_N$

The $y$ force is calculated as the following, where $N_y$ and $M_y$ are the $y$ positions of the neighbour and focal regions, respectively:

$F_y = O \cdot (N_y - M_y) / D_N$

The supplied $x$ and $y$ forces are updated by subtracting the new forces.

Parameters

  • neighbour : pandas.Series - A given Neighbour of focal
  • focal : pandas.Series - The current, focal region
  • xrepel : float - The current repulsive force in the $x$ direction
  • yrepel : float - The current repulsive force in the $y$ direction

Returns

  • xrepel : float - The new repulsive force in the $x$ direction.
  • yrepel : float - The new repulsive force in the $y$ direction.

_attract(nb, borders, idx, focal, perimeter, xattract, yattract)

Calculates and updates the attractive force being applied to a given region, towards a given neighbour region.

Before the attractive forces are calculated, the overlap $O$ amount for a neighbour is scaled as such, where $W_{FN}$ is the Queen contiguity weight and $P_F$ is the perimeter of the focal region:

$O_{new} = (| O_{original} | * W_{FN}) / P_F$

The attractive force $A$ is split into $x$ and $y$ components, $A_x$ and $A_y$ respectively. These forces are calculated as the following, where $O$ is the amount the neighbour overlaps with the focal region, $D_N$ is the distance from the neighbour to the focal region, $N_x$ is the $x$ position of the neighbour and $M_x$ is the $x$ position of the focal region:

$A_x = O_x \cdot (N_x - M_x) / D_N$

The $y$ force is calculated as the following, where $N_y$ and $M_y$ are the $y$ positions of the neighbour and focal regions, respectively:

$A_y = O_y \cdot (N_y - M_y) / D_N$

The supplied $x$ and $y$ forces are updated by summing with the new forces.

Parameters

  • nb : pandas.Series - A given Neighbour of focal
  • borders : pandas.DataFrame - DataFrame of all border weights
  • idx : int - The index of the current region
  • focal : pandas.Series - The current, focal region
  • perimeter : pandas.Series - The perimeters of all regions
  • xattract : float - The current attractive force in the $x$ direction
  • yattract : float - The current attractive force in the $y$ direction

Returns

  • xattract : float - The new attractive force in the $x$ direction.
  • yattract : float - The new attractive force in the $y$ direction.

References

[1] J. M. Olson, "Noncontiguous Area Cartograms," in The Professional Geographer, vol. 28, no. 4, pp. 371-380, 1976.

[2] D. Dorling, "Area Cartograms: Their Use and Creation," in Concepts and Techniques in Modern Geography, no. 59 Environmental Publications, University of East Anglia, pp. 32-36, 1996.