The aim of the package chessboard
is to provide tools to
work with directed (asymmetric) and
undirected (symmetrical) spatial (or non-spatial)
networks. It implements different methods to detect
neighbors, all based on the chess game (it goes beyond the rook and the
queen available in many R packages) to create complex connectivity
scenarios.
chessboard
aims to easily create various network
objects, including:
n x n
, where n
is the number of
nodes (sampling units) indicating the presence
(1
) or the absence (0
) of an
edge (link) between pairs of nodes.
# Setup ----
library("chessboard")
library("ggplot2")
library("patchwork")
chessboard
can handle spatial networks, but it does not
explicitly use geographical coordinates to find neighbors (it is not
based on spatial distance). Instead, it identifies neighbors according
to node labels (i.e. the node position on a
two-dimension chessboard) and a specific method (pawn, fool, rook,
bishop, knight, queen, wizard, etc.) derived from the chess game.
The package chessboard
is designed to work with
two-dimensional networks (i.e. sampling on a regular grid), where one
dimension is called transect and the other is called
quadrat. By convention, the dimension
transect corresponds to the x-axis, and the
quadrat corresponds to the y-axis (Fig. 1).
chessboard
can also deal with one-dimensional network
(either transect-only or
quadrat-only).
The network can be undirected or directed. If the network is directed, it will have (by default) these two orientations:
chessboard
implements the following rules to detect
neighbors and to create edges:
the degree of neighborhood: the number of adjacent nodes that will be used to create direct edges.
the orientation of neighborhood: can neighbors be detected horizontally, vertically and/or diagonally?
the direction of neighborhood: does the sampling has a main direction? This can be particularly relevant for directed networks (e.g. rivers).
The Figure 2 shows the general workflow and the main features of
chessboard
.
The package chessboard
comes with a real-world example:
a survey sampling along the French river L’Adour (Fig. 3).
L’Adour is a river in southwestern France. It rises in the
Pyrenees and flows into the Atlantic Ocean (Bay of Biscay). It’s
oriented from south-east (upstream) to north-west (downstream).
Along this river, a survey has been realized at three locations (Fig. 4). At each location, a sampling has been conducted on a regular grid composed of three transects each of them composed of five quadrats.
The arrow in Fig. 4 indicates the direction of the river flow. This means that our sampling design is a directed spatial network (both inside a location and between locations) where the main direction is from upstream to downstream.
Let’s import this dataset provided by chessboard
.
# Import data ----
path_to_file <- system.file("extdata", "adour_survey_sampling.csv",
package = "chessboard")
sampling <- read.csv(path_to_file)
dim(sampling)
#> [1] 45 5
# First rows ----
head(sampling, 10)
#> location transect quadrat longitude latitude
#> 1 1 1 1 474036.9 6221518
#> 2 1 1 2 473080.0 6223828
#> 3 1 1 3 472123.1 6226138
#> 4 1 1 4 471166.2 6228448
#> 5 1 1 5 470209.3 6230758
#> 6 1 2 1 476347.0 6222475
#> 7 1 2 2 475390.1 6224785
#> 8 1 2 3 474433.2 6227095
#> 9 1 2 4 473476.3 6229405
#> 10 1 2 5 472519.5 6231715
# Last rows ----
tail(sampling, 10)
#> location transect quadrat longitude latitude
#> 36 3 2 11 414434.2 6312095
#> 37 3 2 12 411933.7 6312095
#> 38 3 2 13 409433.2 6312095
#> 39 3 2 14 406932.8 6312095
#> 40 3 2 15 404432.3 6312095
#> 41 3 3 11 414434.2 6314595
#> 42 3 3 12 411933.7 6314595
#> 43 3 3 13 409433.2 6314595
#> 44 3 3 14 406932.8 6314595
#> 45 3 3 15 404432.3 6314595
This data.frame
contains the following columns:
location
: the identifier of the location
(numeric
)transect
: the identifier of the transect
(numeric
)quadrat
: the identifier of the quadrat
(numeric
)longitude
: the longitude of the site
(node) defined in the RGF93 / Lambert-93 projectionlatitude
: the latitude of the site
(node) defined in the RGF93 / Lambert-93 projectionN.B. The column location
is optional if
the survey has been conducted at one single location. If the network has
one dimension, one of the columns transect
and
quadrat
can be omitted. If the survey is not spatial, the
columns longitude
and latitude
can be
omitted.
When working with chessboard
, the first
step is to create node labels with the function
create_node_labels()
.
But first, let’s reduce the size of data by selecting the first location.
# Select the first location ----
sampling <- sampling[sampling$"location" == 1, ]
dim(sampling)
#> [1] 15 5
Let’s create node labels with the function
create_node_labels()
.
# Create node labels ----
nodes <- create_node_labels(data = sampling,
location = "location",
transect = "transect",
quadrat = "quadrat")
nodes
#> node location transect quadrat longitude latitude
#> 1 1-1 1 1 1 474036.9 6221518
#> 2 1-2 1 1 2 473080.0 6223828
#> 3 1-3 1 1 3 472123.1 6226138
#> 4 1-4 1 1 4 471166.2 6228448
#> 5 1-5 1 1 5 470209.3 6230758
#> 6 2-1 1 2 1 476347.0 6222475
#> 7 2-2 1 2 2 475390.1 6224785
#> 8 2-3 1 2 3 474433.2 6227095
#> 9 2-4 1 2 4 473476.3 6229405
#> 10 2-5 1 2 5 472519.5 6231715
#> 11 3-1 1 3 1 478657.1 6223432
#> 12 3-2 1 3 2 477700.2 6225742
#> 13 3-3 1 3 3 476743.4 6228052
#> 14 3-4 1 3 4 475786.5 6230362
#> 15 3-5 1 3 5 474829.6 6232672
Node labels are a combination of the transect and the quadrat identifiers. They must be unique.
We can visualize this sampling on a Cartesian referential, i.e. non-spatial, where the x-axis corresponds to transects and the y-axis represents the quadrats (Fig. 5). This new referential is called a chessboard.
# Visualize chessboard ----
gg_chessboard(nodes)
The function get_node_list()
can be used to extract and
order the node list.
# Extract node labels ----
get_node_list(nodes)
#> [1] "1-1" "1-2" "1-3" "1-4" "1-5" "2-1" "2-2" "2-3" "2-4" "2-5" "3-1" "3-2"
#> [13] "3-3" "3-4" "3-5"
The creation of a list of edges (links) between nodes (sampling units) is based on the detection of neighbors.
In chessboard
different methods have been implemented to
define neighborhood (argument method
of the function
create_edge_list()
). These methods are named
'pawn'
, 'rook'
, 'bishop'
,
'queen'
, etc. A complete list of available methods is
available at: https://frbcesab.github.io/chessboard/reference/index.html#detect-neighbors
Before using the function create_edge_list()
, users can
explore these different methods by calling the functions
pawn()
, rook()
, bishop()
,
queen()
, etc. These functions only work on a specific node
(argument focus
).
Let’s take a look of the neighborhood method pawn()
.
# Explore pawn method to find neighbors ----
neighbors_pawn <- pawn(nodes = nodes,
focus = "2-3",
degree = 1,
directed = FALSE,
reverse = FALSE)
neighbors_pawn
#> node location transect quadrat longitude latitude
#> 1 2-2 1 2 2 475390.1 6224785
#> 2 2-4 1 2 4 473476.3 6229405
The package chessboard
contains functions to visualize
detected neighbors on a chessboard: gg_chessboard()
is used
to plot a chessboard (dimensions defined by the node list),
geom_node()
emphasizes the focus node (in red), and
geom_neighbors()
adds the detected neighbors (dots in
black).
gg_chessboard(nodes) +
geom_node(nodes, focus = "2-3") +
geom_neighbors(nodes, neighbors_pawn)
The function pawn()
can detect neighbors vertically,
i.e. among quadrats along a transect. User can change the default
settings by increasing the degree of neighborhood
(degree = 4
, Fig. 7A), by adding directionality
(directed = TRUE
, Fig. 7B), and by reversing the default
directionality (directed = TRUE
and
reverse = TRUE
, Fig. 7C).
Let’s take another example. The function bishop()
can
detect neighbors diagonally. User can change the default settings by
increasing the degree of neighborhood (degree = 4
, Fig.
8A), by adding directionality (directed = TRUE
, Fig. 8B),
and by reversing the default directionality
(directed = TRUE
and reverse = TRUE
, Fig.
8C).
The vignette Chess
pieces shows all possible methods available in
chessboard
.
Now, let’s use the function create_edge_list()
to create
an edge list using the method 'pawn'
with a degree
1
of neighborhood and in a directional way.
# Create edge list ----
edges_pawn <- create_edge_list(nodes = nodes,
method = "pawn",
degree = 1,
directed = TRUE,
reverse = FALSE,
self = FALSE)
edges_pawn
#> from to
#> 1 1-1 1-2
#> 2 1-2 1-3
#> 3 1-3 1-4
#> 4 1-4 1-5
#> 5 2-1 2-2
#> 6 2-2 2-3
#> 7 2-3 2-4
#> 8 2-4 2-5
#> 9 3-1 3-2
#> 10 3-2 3-3
#> 11 3-3 3-4
#> 12 3-4 3-5
It’s possible to visualize these edges on a map, i.e. by using
spatial coordinates. First, we need to convert our sites into a spatial
object (POINT
).
# Convert nodes to sf object ----
nodes_sf <- sf::st_as_sf(nodes, coords = c("longitude", "latitude"),
crs = "epsg:2154")
head(nodes_sf)
#> Simple feature collection with 6 features and 4 fields
#> Geometry type: POINT
#> Dimension: XY
#> Bounding box: xmin: 470209.3 ymin: 6221518 xmax: 476347 ymax: 6230758
#> Projected CRS: RGF93 v1 / Lambert-93
#> node location transect quadrat geometry
#> 1 1-1 1 1 1 POINT (474036.9 6221518)
#> 2 1-2 1 1 2 POINT (473080 6223828)
#> 3 1-3 1 1 3 POINT (472123.1 6226138)
#> 4 1-4 1 1 4 POINT (471166.2 6228448)
#> 5 1-5 1 1 5 POINT (470209.3 6230758)
#> 6 2-1 1 2 1 POINT (476347 6222475)
Now we can use the function edges_to_sf()
to convert our
edge list into a spatial object (LINESTRING
).
# Convert edge list to sf ----
edges_pawn_sf <- edges_to_sf(edges = edges_pawn,
sites = nodes_sf)
edges_pawn_sf
#> Simple feature collection with 12 features and 2 fields
#> Geometry type: LINESTRING
#> Dimension: XY
#> Bounding box: xmin: 470209.3 ymin: 6221518 xmax: 478657.1 ymax: 6232672
#> Projected CRS: RGF93 v1 / Lambert-93
#> First 10 features:
#> from to geometry
#> 1 1-1 1-2 LINESTRING (474036.9 622151...
#> 2 1-2 1-3 LINESTRING (473080 6223828,...
#> 3 1-3 1-4 LINESTRING (472123.1 622613...
#> 4 1-4 1-5 LINESTRING (471166.2 622844...
#> 5 2-1 2-2 LINESTRING (476347 6222475,...
#> 6 2-2 2-3 LINESTRING (475390.1 622478...
#> 7 2-3 2-4 LINESTRING (474433.2 622709...
#> 8 2-4 2-5 LINESTRING (473476.3 622940...
#> 9 3-1 3-2 LINESTRING (478657.1 622343...
#> 10 3-2 3-3 LINESTRING (477700.2 622574...
We can now map our nodes and edges.
# Map of nodes and edges ----
ggplot(nodes_sf) +
geom_sf(size = 12) +
geom_sf(data = edges_pawn_sf) +
theme_light()
Users may want to combine different methods to detect neighbors to
build complex scenarios. It’s possible by using for each method the
function create_edge_list()
and using the function
append_edge_lists()
to merge all edges in a single
list.
# Create edge list (Bishop method) ----
edges_bishop <- create_edge_list(nodes = nodes,
method = "bishop",
degree = 1,
directed = TRUE,
reverse = FALSE,
self = FALSE)
edges_bishop
#> from to
#> 1 1-1 2-2
#> 2 1-2 2-3
#> 3 1-3 2-4
#> 4 1-4 2-5
#> 5 2-1 1-2
#> 6 2-1 3-2
#> 7 2-2 1-3
#> 8 2-2 3-3
#> 9 2-3 1-4
#> 10 2-3 3-4
#> 11 2-4 1-5
#> 12 2-4 3-5
#> 13 3-1 2-2
#> 14 3-2 2-3
#> 15 3-3 2-4
#> 16 3-4 2-5
# Merge Pawn and Bishop edges ----
edges <- append_edge_lists(edges_pawn, edges_bishop)
# Convert edges to spatial layer ----
edges_sf <- edges_to_sf(edges, nodes_sf)
# Map of nodes and edges ----
ggplot(nodes_sf) +
geom_sf(size = 12) +
geom_sf(data = edges_sf) +
theme_light()
From this edge list, we can build a connectivity
matrix, i.e. a binary matrix of dimensions n x n
,
where n
is the number of nodes (sampling units) indicating
the presence (1
) or the absence (0
) of an edge
(link) between pairs of nodes.
We can use the function connectivity_matrix()
of the
package chessboard
.
# Create connectivity matrix ----
conn_matrix <- connectivity_matrix(edges)
conn_matrix
#> 1-1 1-2 1-3 1-4 1-5 2-1 2-2 2-3 2-4 2-5 3-1 3-2 3-3 3-4 3-5
#> 1-1 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0
#> 1-2 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0
#> 1-3 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0
#> 1-4 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0
#> 1-5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
#> 2-1 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0
#> 2-2 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0
#> 2-3 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0
#> 2-4 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
#> 2-5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
#> 3-1 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0
#> 3-2 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0
#> 3-3 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0
#> 3-4 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1
#> 3-5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
The package chessboard
provides a function to visualize
this matrix: gg_matrix()
.
# Visualize connectivity matrix ----
gg_matrix(conn_matrix)
Optionally, we can use the function
matrix_to_edge_list()
to convert back the connectivity
matrix to edge list.
# Convert connectivity matrix to edge list ----
matrix_to_edge_list(conn_matrix)
#> from to edge
#> 1 1-1 1-2 1
#> 2 1-1 2-2 1
#> 3 1-2 1-3 1
#> 4 1-2 2-3 1
#> 5 1-3 1-4 1
#> 6 1-3 2-4 1
#> 7 1-4 1-5 1
#> 8 1-4 2-5 1
#> 9 2-1 1-2 1
#> 10 2-1 2-2 1
#> 11 2-1 3-2 1
#> 12 2-2 1-3 1
#> 13 2-2 2-3 1
#> 14 2-2 3-3 1
#> 15 2-3 1-4 1
#> 16 2-3 2-4 1
#> 17 2-3 3-4 1
#> 18 2-4 1-5 1
#> 19 2-4 2-5 1
#> 20 2-4 3-5 1
#> 21 3-1 2-2 1
#> 22 3-1 3-2 1
#> 23 3-2 2-3 1
#> 24 3-2 3-3 1
#> 25 3-3 2-4 1
#> 26 3-3 3-4 1
#> 27 3-4 2-5 1
#> 28 3-4 3-5 1
chessboard
has been built to be compatible with the
following R packages: igraph
(Csardi &
Nepusz 2006), sf
(Pebesma
2018), and ggplot2
(Wickham
2016).
sf
As seen before, the edge list can be converted into an spatial object
with the function edges_to_sf()
. The output is an
sf
LINESTRING
that can be handled by many
functions of the package sf
. For instance, let’s project
the coordinate system.
# Convert edges to spatial layer ----
edges_sf <- edges_to_sf(edges, nodes_sf)
# Project the CRS ----
edges_sf_lonlat <- sf::st_transform(edges_sf, crs = "epsg:4326")
# Check ----
edges_sf
#> Simple feature collection with 28 features and 2 fields
#> Geometry type: LINESTRING
#> Dimension: XY
#> Bounding box: xmin: 470209.3 ymin: 6221518 xmax: 478657.1 ymax: 6232672
#> Projected CRS: RGF93 v1 / Lambert-93
#> First 10 features:
#> from to geometry
#> 1 1-1 1-2 LINESTRING (474036.9 622151...
#> 2 1-1 2-2 LINESTRING (474036.9 622151...
#> 3 1-2 1-3 LINESTRING (473080 6223828,...
#> 4 1-2 2-3 LINESTRING (473080 6223828,...
#> 5 1-3 1-4 LINESTRING (472123.1 622613...
#> 6 1-3 2-4 LINESTRING (472123.1 622613...
#> 7 1-4 1-5 LINESTRING (471166.2 622844...
#> 8 1-4 2-5 LINESTRING (471166.2 622844...
#> 9 2-1 1-2 LINESTRING (476347 6222475,...
#> 10 2-1 2-2 LINESTRING (476347 6222475,...
edges_sf_lonlat
#> Simple feature collection with 28 features and 2 fields
#> Geometry type: LINESTRING
#> Dimension: XY
#> Bounding box: xmin: 0.1770395 ymin: 43.05727 xmax: 0.2838317 ymax: 43.15778
#> Geodetic CRS: WGS 84
#> First 10 features:
#> from to geometry
#> 1 1-1 1-2 LINESTRING (0.2280052 43.05...
#> 2 1-1 2-2 LINESTRING (0.2280052 43.05...
#> 3 1-2 1-3 LINESTRING (0.2152773 43.07...
#> 4 1-2 2-3 LINESTRING (0.2152773 43.07...
#> 5 1-3 1-4 LINESTRING (0.2025404 43.09...
#> 6 1-3 2-4 LINESTRING (0.2025404 43.09...
#> 7 1-4 1-5 LINESTRING (0.1897944 43.11...
#> 8 1-4 2-5 LINESTRING (0.1897944 43.11...
#> 9 2-1 1-2 LINESTRING (0.2559139 43.06...
#> 10 2-1 2-2 LINESTRING (0.2559139 43.06...
User can also export this spatial layer.
# Export layer as a GeoPackage ----
sf::st_write(edges_sf, "edge_list.gpkg")
For more information about the package sf
, please visit
the manual.
ggplot2
All plotting functions in chessboard
are produced with
the ggplot2
engine and are highly customizable. For
instance, let’s change the default theme.
# Change default ggplot2 theme ----
gg_matrix(conn_matrix) +
theme_bw() +
theme(legend.position = "none")
For more information about the package ggplot2
, please
visit the manual.
igraph
The package igraph
is commonly use to analyze network
data. User can use the function
igraph::graph_from_data_frame()
to convert the edge list
created with chessboard
to an igraph
object.
# Convert edge list to igraph object ----
igraph_obj <- igraph::graph_from_data_frame(d = edges,
directed = TRUE,
vertices = nodes)
# Check -----
class(igraph_obj)
#> [1] "igraph"
print(igraph_obj)
#> IGRAPH 1c8828a DN-- 15 28 --
#> + attr: name (v/c), location (v/n), transect (v/n), quadrat (v/n),
#> | longitude (v/n), latitude (v/n)
#> + edges from 1c8828a (vertex names):
#> [1] 1-1->1-2 1-1->2-2 1-2->1-3 1-2->2-3 1-3->1-4 1-3->2-4 1-4->1-5 1-4->2-5
#> [9] 2-1->1-2 2-1->2-2 2-1->3-2 2-2->1-3 2-2->2-3 2-2->3-3 2-3->1-4 2-3->2-4
#> [17] 2-3->3-4 2-4->1-5 2-4->2-5 2-4->3-5 3-1->2-2 3-1->3-2 3-2->2-3 3-2->3-3
#> [25] 3-3->2-4 3-3->3-4 3-4->2-5 3-4->3-5
Let’s plot our network using igraph
.
# Plot the network w/ igraph ----
plot(igraph_obj)
For more information about the package igraph
, please
visit the manual.
Bivand R & Wong D (2018) Comparing implementations of global and local indicators of spatial association. TEST, 27, 716–748. https://doi.org/10.1007/s11749-018-0599-x.
Csardi G & Nepusz T (2006) The igraph software package for complex network research. InterJournal, Complex Systems, 1695, 1–9. https://igraph.org/.
Pebesma E (2018) Simple Features for R: Standardized support for spatial vector data. The R Journal, 10, 439–446. https://doi.org/10.32614/RJ-2018-009.
Wickham H (2016) ggplot2: Elegant graphics for data analysis (p. 213). Springer-Verlag. https://ggplot2.tidyverse.org/.