<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/three-ways-of-visualizing-a-graph-on-a-map/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://datascience.blog.wzb.eu/2018/05/31/three-ways-of-visualizing-a-graph-on-a-map/"> r-bloggers – WZB Data Science Blog</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>When visualizing a network with nodes that refer to a geographic place, it is often useful to put these nodes on a map and draw the connections <em>(edges)</em> between them. By this, we can directly see the geographic distribution of nodes and their connections in our network. This is different to a traditional network plot, where the placement of the nodes depends on the layout algorithm that is used (which may for example form clusters of strongly interconnected nodes).</p>
<p>In this blog post, I’ll present three ways of visualizing network graphs on a map using R with the packages <em><a rel="nofollow" href="http://igraph.org/r/" target="_blank">igraph</a></em>, <em><a rel="nofollow" href="http://ggplot2.tidyverse.org/" target="_blank">ggplot2</a></em> and optionally <em><a rel="nofollow" href="https://github.com/thomasp85/ggraph" target="_blank">ggraph</a></em>. Several properties of our graph should be visualized along with the positions on the map and the connections between them. Specifically, the size of a node on the map should reflect its <a rel="nofollow" href="http://igraph.org/r/doc/degree.html" target="_blank">degree</a>, the width of an edge between two nodes should represent the weight (strength) of this connection (since we can’t use proximity to illustrate the strength of a connection when we place the nodes on a map), and the color of an edge should illustrate the type of connection (some categorical variable, e.g. a type of treaty between two international partners).</p>
<p><span></span></p>
<h3>Preparation</h3>
<p>We’ll need to load the following libraries first:</p>
<pre><code>library(assertthat)
library(dplyr)
library(purrr)
library(igraph)
library(ggplot2)
library(ggraph)
library(ggmap)
</code></pre>
<p>Now, let’s load some example nodes. I’ve picked some random countries with their geo-coordinates:</p>
<pre><code>country_coords_txt &lt;- &quot;
 1     3.00000  28.00000       Algeria
 2    54.00000  24.00000           UAE
 3   139.75309  35.68536         Japan
 4    45.00000  25.00000 'Saudi Arabia'
 5     9.00000  34.00000       Tunisia
 6     5.75000  52.50000   Netherlands
 7   103.80000   1.36667     Singapore
 8   124.10000  -8.36667         Korea
 9    -2.69531  54.75844            UK
10    34.91155  39.05901        Turkey
11  -113.64258  60.10867        Canada
12    77.00000  20.00000         India
13    25.00000  46.00000       Romania
14   135.00000 -25.00000     Australia
15    10.00000  62.00000        Norway&quot;

# nodes come from the above table and contain geo-coordinates for some
# randomly picked countries
nodes &lt;- read.delim(text = country_coords_txt, header = FALSE,
                    quote = &quot;'&quot;, sep = &quot;&quot;,
                    col.names = c('id', 'lon', 'lat', 'name'))
</code></pre>
<p>So we now have 15 countries, each with an ID, geo-coordinates (<code>lon</code> and <code>lat</code>) and a name. These are our graph nodes. We’ll now create some random connections (<code>edges</code>) between our nodes:</p>
<pre><code>set.seed(123)  # set random generator state for the same output

N_EDGES_PER_NODE_MIN &lt;- 1
N_EDGES_PER_NODE_MAX &lt;- 4
N_CATEGORIES &lt;- 4

# edges: create random connections between countries (nodes)
edges &lt;- map_dfr(nodes$id, function(id) {
  n &lt;- floor(runif(1, N_EDGES_PER_NODE_MIN, N_EDGES_PER_NODE_MAX+1))
  to &lt;- sample(1:max(nodes$id), n, replace = FALSE)
  to &lt;- to[to != id]
  categories &lt;- sample(1:N_CATEGORIES, length(to), replace = TRUE)
  weights &lt;- runif(length(to))
  data_frame(from = id, to = to, weight = weights, category = categories)
})

edges &lt;- edges %>% mutate(category = as.factor(category))
</code></pre>
<p>Each of these edges defines a connection via the node IDs in the <code>from</code> and <code>to</code> columns and additionally we generated random connection <code>categories</code> and <code>weights</code>. Such properties are often used in graph analysis and will later be visualized too.</p>
<p>Our nodes and edges fully describe a graph so we can now generate a graph structure <code>g</code> with the igraph library. This is especially necessary for fast calculation of the <em>degree</em> or other properties of each node later.</p>
<pre><code>g &lt;- graph_from_data_frame(edges, directed = FALSE, vertices = nodes)
</code></pre>
<p>We now create some data structures that will be needed for all the plots that we will generate. At first, we create a data frame for plotting the edges. This data frame will be the same like the <code>edges</code> data frame but with four additional columns that define the start and end points for each edge (<code>x</code>, <code>y</code> and <code>xend</code>, <code>yend</code>):</p>
<pre><code>edges_for_plot &lt;- edges %>%
  inner_join(nodes %>% select(id, lon, lat), by = c('from' = 'id')) %>%
  rename(x = lon, y = lat) %>%
  inner_join(nodes %>% select(id, lon, lat), by = c('to' = 'id')) %>%
  rename(xend = lon, yend = lat)

assert_that(nrow(edges_for_plot) == nrow(edges))
</code></pre>
<p>Let’s give each node a weight and use the degree metric for this. This will be reflected by the node sizes on the map later.</p>
<pre><code>nodes$weight = degree(g)
</code></pre>
<p>Now we define a common <a rel="nofollow" href="http://ggplot2.tidyverse.org/reference/index.html#section-themes" target="_blank"><em>ggplot2</em> theme</a> that is suitable for displaying maps (sans axes and grids):</p>
<pre><code>maptheme &lt;- theme(panel.grid = element_blank()) +
  theme(axis.text = element_blank()) +
  theme(axis.ticks = element_blank()) +
  theme(axis.title = element_blank()) +
  theme(legend.position = &quot;bottom&quot;) +
  theme(panel.grid = element_blank()) +
  theme(panel.background = element_rect(fill = &quot;#596673&quot;)) +
  theme(plot.margin = unit(c(0, 0, 0.5, 0), 'cm'))
</code></pre>
<p>Not only the theme will be the same for all plots, but they will also share the same world map as “background” (using <code>map_data('world')</code>) and the same fixed ratio coordinate system that also specifies the limits of the longitude and latitude coordinates.</p>
<pre><code>country_shapes &lt;- geom_polygon(aes(x = long, y = lat, group = group),
                               data = map_data('world'),
                               fill = &quot;#CECECE&quot;, color = &quot;#515151&quot;,
                               size = 0.15)
mapcoords &lt;- coord_fixed(xlim = c(-150, 180), ylim = c(-55, 80))
</code></pre>
<h3>Plot 1: Pure ggplot2</h3>
<p>Let’s start simple by using ggplot2. We’ll need three geometric objects <em>(<a rel="nofollow" href="http://ggplot2.tidyverse.org/reference/index.html#section-layer-geoms" target="_blank">geoms</a>)</em> additional to the country polygons from the world map (<code>country_shapes</code>): Nodes can be drawn as points using <code>geom_point</code> and their labels with <code>geom_text</code>; edges between nodes can be realized as curves using <code>geom_curve</code>. For each <em>geom</em> we need to define <a rel="nofollow" href="http://ggplot2.tidyverse.org/reference/aes.html" target="_blank"><em>aesthetic mappings</em> that “describe how variables in the data are mapped to visual properties”</a> in the plot. For the nodes we map the geo-coordinates to the <em>x</em> and <em>y</em> positions in the plot and make the node size dependent on its weight (<code>aes(x = lon, y = lat, size = weight)</code>). For the edges, we pass our <code>edges_for_plot</code> data frame and use the <code>x</code>, <code>y</code> and <code>xend</code>, <code>yend</code> as start and end points of the curves. Additionally, we make each edge’s color dependent on its <code>category</code>, and its “size” (which refers to its line width) dependent on the edges’ weights (we will see that the latter will fail). Note that the order of the geoms is important as it defines which object is drawn first and can be occluded by an object that is drawn later in the next geom layer. Hence we draw the edges first and then the node points and finally the labels on top:</p>
<pre><code>ggplot(nodes) + country_shapes +
  geom_curve(aes(x = x, y = y, xend = xend, yend = yend,     # draw edges as arcs
                 color = category, size = weight),
             data = edges_for_plot, curvature = 0.33,
             alpha = 0.5) +
  scale_size_continuous(guide = FALSE, range = c(0.25, 2)) + # scale for edge widths
  geom_point(aes(x = lon, y = lat, size = weight),           # draw nodes
             shape = 21, fill = 'white',
             color = 'black', stroke = 0.5) +
  scale_size_continuous(guide = FALSE, range = c(1, 6)) +    # scale for node size
  geom_text(aes(x = lon, y = lat, label = name),             # draw text labels
            hjust = 0, nudge_x = 1, nudge_y = 4,
            size = 3, color = &quot;white&quot;, fontface = &quot;bold&quot;) +
  mapcoords + maptheme
</code></pre>
<p><a rel="nofollow" href="https://i1.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot1a.png?ssl=1" target="_blank"><img sizes="(max-width: 640px) 100vw, 640px" src="https://i0.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot1a-1024x766.png?w=450&ssl=1" alt="" class="wp-image-794" data-recalc-dims="1"></a></p>
<p>A warning will be displayed in the console saying <em>“Scale for ‘size’ is already present. Adding another scale for ‘size’, which will replace the existing scale.”</em>. This is because we used the “size” aesthetic and its scale twice, once for the node size and once for the line width of the curves. Unfortunately you cannot use two different scales for the same aesthetic even when they’re used for different geoms (here: “size” for both node size and the edges’ line widths). There is also no alternative to “size” I know of for controlling a line’s width in ggplot2.</p>
<p>With ggplot2, we’re left of with deciding which geom’s size we want to scale. Here, I go for a static node size and a dynamic line width for the edges:</p>
<pre><code>ggplot(nodes) + country_shapes +
  geom_curve(aes(x = x, y = y, xend = xend, yend = yend,     # draw edges as arcs
                 color = category, size = weight),
             data = edges_for_plot, curvature = 0.33,
             alpha = 0.5) +
  scale_size_continuous(guide = FALSE, range = c(0.25, 2)) + # scale for edge widths
  geom_point(aes(x = lon, y = lat),                          # draw nodes
             shape = 21, size = 3, fill = 'white',
             color = 'black', stroke = 0.5) +
  geom_text(aes(x = lon, y = lat, label = name),             # draw text labels
            hjust = 0, nudge_x = 1, nudge_y = 4,
            size = 3, color = &quot;white&quot;, fontface = &quot;bold&quot;) +
  mapcoords + maptheme
</code></pre>
<p><a rel="nofollow" href="https://i2.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot1b.png?ssl=1" target="_blank"><img sizes="(max-width: 640px) 100vw, 640px" src="https://i0.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot1b-1024x766.png?w=450&ssl=1" alt="" class="wp-image-797" data-recalc-dims="1"></a></p>
<h3>Plot 2: ggplot2 + ggraph</h3>
<p>Luckily, there is an extension to ggplot2 called <em>ggraph</em> with geoms and aesthetics added specifically for plotting network graphs. This allows us to use separate scales for the nodes and edges. By default, ggraph will place the nodes according to a layout algorithm that you can specify. However, we can also define our own custom layout using the geo-coordinates as node positions:</p>
<pre><code>node_pos &lt;- nodes %>%
  select(lon, lat) %>%
  rename(x = lon, y = lat)   # node positions must be called x, y
lay &lt;- create_layout(g, 'manual',
                     node.positions = node_pos)
assert_that(nrow(lay) == nrow(nodes))

# add node degree for scaling the node sizes
lay$weight &lt;- degree(g)
</code></pre>
<p>We pass the layout <code>lay</code> and use ggraph’s geoms <code>geom_edge_arc</code> and <code>geom_node_point</code> for plotting:</p>
<pre><code>ggraph(lay) + country_shapes +
  geom_edge_arc(aes(color = category, edge_width = weight,   # draw edges as arcs
                    circular = FALSE),
                data = edges_for_plot, curvature = 0.33,
                alpha = 0.5) +
  scale_edge_width_continuous(range = c(0.5, 2),             # scale for edge widths
                              guide = FALSE) +
  geom_node_point(aes(size = weight), shape = 21,            # draw nodes
                  fill = &quot;white&quot;, color = &quot;black&quot;,
                  stroke = 0.5) +
  scale_size_continuous(range = c(1, 6), guide = FALSE) +    # scale for node sizes
  geom_node_text(aes(label = name), repel = TRUE, size = 3,
                 color = &quot;white&quot;, fontface = &quot;bold&quot;) +
  mapcoords + maptheme
</code></pre>
<p><a rel="nofollow" href="https://i1.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot2.png?ssl=1" target="_blank"><img sizes="(max-width: 640px) 100vw, 640px" src="https://i2.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot2-1024x766.png?w=450&ssl=1" alt="" class="wp-image-801" data-recalc-dims="1"></a></p>
<p>The edges’ widths can be controlled with the <code>edge_width</code> aesthetic and its scale functions <code>scale_edge_width_*</code>. The nodes’ sizes are controlled with <code>size</code> as before. Another nice feature is that <code>geom_node_text</code> has an option to distribute node labels with <code>repel = TRUE</code> so that they do not occlude each other that much.</p>
<p>Note that the plot’s edges are differently drawn than with the ggplot2 graphics before. The connections are still the same only the placement is different due to different layout algorithms that are used by ggraph. For example, the turquoise edge line between Canada and Japan has moved from the very north to south across the center of Africa.</p>
<h3>Plot 3: the hacky way (overlay several ggplot2 “plot grobs”)</h3>
<p>I do not want to withhold another option which may be considered a dirty hack: You can overlay several separately created plots (with transparent background) by annotating them as “grobs” (short for “graphical objects”). This is probably not how <a rel="nofollow" href="http://ggplot2.tidyverse.org/reference/annotation_custom.html" target="_blank">grob annotations</a> should be used, but anyway it can come in handy when you really need to overcome the aesthetics limitation of ggplot2 described above in plot 1.</p>
<p>As explained, we will produce separate plots and “stack” them. The first plot will be the “background” which displays the world map as before. The second plot will be an overlay that only displays the edges. Finally, a third overlay shows only the points for the nodes and their labels. With this setup, we can control the edges’ line widths and the nodes’ point sizes separately because they are generated in separate plots.</p>
<p>The two overlays need to have a transparent background so we define it with a theme:</p>
<pre><code>theme_transp_overlay &lt;- theme(
  panel.background = element_rect(fill = &quot;transparent&quot;, color = NA),
  plot.background = element_rect(fill = &quot;transparent&quot;, color = NA)
)
</code></pre>
<p>The base or “background” plot is easy to make and only shows the map:</p>
<pre><code>p_base &lt;- ggplot() + country_shapes + mapcoords + maptheme
</code></pre>
<p><a rel="nofollow" href="https://i1.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot3_base.png?ssl=1" target="_blank"><img sizes="(max-width: 640px) 100vw, 640px" src="https://i0.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot3_base-1024x766.png?w=450&ssl=1" alt="" class="wp-image-806" data-recalc-dims="1"></a></p>
<p>Now we create the first overlay with the edges whose line width is scaled according to the edges’ weights:</p>
<pre><code>p_edges &lt;- ggplot(edges_for_plot) +
  geom_curve(aes(x = x, y = y, xend = xend, yend = yend,     # draw edges as arcs
                 color = category, size = weight),
             curvature = 0.33, alpha = 0.5) +
  scale_size_continuous(guide = FALSE, range = c(0.5, 2)) +  # scale for edge widths
  mapcoords + maptheme + theme_transp_overlay +
  theme(legend.position = c(0.5, -0.1),
        legend.direction = &quot;horizontal&quot;)
</code></pre>
<p><a rel="nofollow" href="https://i1.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot3_edges.png?ssl=1" target="_blank"><img sizes="(max-width: 640px) 100vw, 640px" src="https://i2.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot3_edges-1024x766.png?w=450&ssl=1" alt="" class="wp-image-807" data-recalc-dims="1"></a></p>
<p>The second overlay shows the node points and their labels:</p>
<pre><code>p_nodes &lt;- ggplot(nodes) +
  geom_point(aes(x = lon, y = lat, size = weight),
             shape = 21, fill = &quot;white&quot;, color = &quot;black&quot;,    # draw nodes
             stroke = 0.5) +
  scale_size_continuous(guide = FALSE, range = c(1, 6)) +    # scale for node size
  geom_text(aes(x = lon, y = lat, label = name),             # draw text labels
            hjust = 0, nudge_x = 1, nudge_y = 4,
            size = 3, color = &quot;white&quot;, fontface = &quot;bold&quot;) +
  mapcoords + maptheme + theme_transp_overlay
</code></pre>
<p><a rel="nofollow" href="https://i0.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot3_nodes.png?ssl=1" target="_blank"><img sizes="(max-width: 640px) 100vw, 640px" src="https://i0.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot3_nodes-1024x766.png?w=450&ssl=1" alt="" class="wp-image-808" data-recalc-dims="1"></a></p>
<p>Finally we combine the overlays using grob annotations. Note that proper positioning of the grobs can be tedious. I found that using <code>ymin</code> works quite well but manual tweaking of the parameter seems necessary.</p>
<pre><code>p &lt;- p_base +
  annotation_custom(ggplotGrob(p_edges), ymin = -74) +
  annotation_custom(ggplotGrob(p_nodes), ymin = -74)

print(p)
</code></pre>
<p><a rel="nofollow" href="https://i2.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot3.png?ssl=1" target="_blank"><img sizes="(max-width: 640px) 100vw, 640px" src="https://i2.wp.com/datascience.blog.wzb.eu/wp-content/uploads/10/2018/05/networkmap_plot3-1024x766.png?w=450&ssl=1" alt="" class="wp-image-805" data-recalc-dims="1"></a></p>
<p>As explained before, this is a hacky solution and should be used with care. Still it is useful also in other circumstances. For example when you need to use different scales for point sizes and line widths in line graphs or need to use different color scales in a single plot this way might be an option to consider.</p>
<p>All in all, network graphs displayed on maps can be useful to show connections between the nodes in your graph on a geographic scale. A downside is that it can look quite cluttered when you have many geographically close points and many overlapping connections. It can be useful then to show only certain details of a map or add some <a rel="nofollow" href="http://ggplot2.tidyverse.org/reference/position_jitter.html" target="_blank">jitter</a> to the edges’ anchor points.</p>
<p>The full R script is available as <a rel="nofollow" href="https://gist.github.com/internaut/a9a274c72181eaa7f5c3ab3a5f54b996" target="_blank">gist on github</a>.</p>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://datascience.blog.wzb.eu/2018/05/31/three-ways-of-visualizing-a-graph-on-a-map/"> r-bloggers – WZB Data Science Blog</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/Sy6QcT86Fkw" height="1"></a></div>