forked from dyerlab/applied_population_genetics
-
Notifications
You must be signed in to change notification settings - Fork 0
/
popgraph_package.rmd
538 lines (320 loc) · 22.8 KB
/
popgraph_package.rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# Population Graphs
Population Graphs are a statistical representation of among population genetic variance, $\sigma^2_A$, as viewed through a network (Dyer & Nason 2004).
The **popgraph** package is designed to take multivariate data and construct a Population Graph (Dyer & Nason 2004). This is a graph-theoretic interpretation of genetic covariance and serves as a tool for understanding underlying evolutionary history for a set of populations.
These routines were originally in the **gstudio** package but were excised out for simplicity. This analysis is *not* limited solely to genetic data and can be used generally for many types of analyses. As such, I pulled this out of the genetic package and allow it to remain on its own. To get your data using **gstudio** with genotypes and such into a format for this package, translate the genotypes into their multivariate format as:
```{r,echo=TRUE,eval=FALSE}
data <- as.matrix( my_genetic_data )
```
For more information on this, see the documentation on the **gstudio** package (a copy is mirrored at http://dyerlab.org/ and a clone of the package can be checked out at https://github.com/dyerlab/gstudio)
## Creating Population Graphs
There are two ways to create a population graph:
1. In this package using the function *popgraph()* and,
+ Via the servers at http://dyerlab.org (which use this packages to do the translation) or via GeneticStudio (an older software package)
```{r,message=FALSE, warning=FALSE}
library(popgraph)
library(igraph)
```
### Creating *De Novo* Graphs
Here we will focus on the former approach as it is native to this package. If you use the latter one, it will produce a *.pgraph file and you can read it in using
```{r}
A <- matrix(0, nrow=5, ncol=5)
A[1,2] <- A[2,3] <- A[1,3] <- A[3,4] <- A[4,5] <- 1
A <- A + t(A)
A
```
There is a quick function, `as.popgraph()` that takes either an existing **igraph**object or a matrix and turns them into *popgraph* objects.
```{r}
g <- as.popgraph( A )
```
There are several options available under the `mode` parameter. We typically use the undirected graph option but the following are also available:
1. `undirected` The connections between nodes are symmetric. This is the default for population graphs as covariance, the quantity the edge is representing is symmetrical.
+ `directed` The edges are asymetric.
+ `max` or `min` Will take the largest (or smallest) value of the matrix (e.g., $max(A[i,j], A[j,i])$ or $min( A[i,j], A[j,i])$ ).
+ `upper` or `lower` Uses either the upper or lower element of the matrix.
+ `plus` Adds upper and lower values (e.g., $A[i,j] + A[j,i]$).
There are many other ways to create **igraph**objects *de novo* but this is the easiest method.
#### Node & Edge Attributes
The underlying structure of an **igraph**object allows you to assoicate attributes (e.g., other data) with nodes and edges. Node attributes are accessed using the $V(graph)$ operator (for vertex) and edge attributes are done via $E(graph)$. Attributes can be set as well as retrieved using the same mechanisms.
```{r}
V(g)$name <- c("Olympia","Bellingham","St. Louis","Ames","Richmond")
V(g)$group <- c("West","West", "Central","Central","East")
V(g)$color <- "#cca160"
list.vertex.attributes( g )
V(g)$name
```
```{r}
E(g)
E(g)$color <- c("red","red", "red", "blue","dark green")
list.edge.attributes( g )
```
### Adding data to a graph
A population graph is made more informative if you can associate some data with topology. External data may be spatial or ecolgoical data associated with each node. Edge data may be a bit more complicated as it is traversing both spatial and ecolgoical gradients and below we'll see how to extract particular from rasters using edge crossings.
Included in the **popgraph** package are some build-in data sets. You can load these into R using the `data()` function as:
```{r}
data(lopho)
class(lopho)
lopho
```
The function `decorate_graph()` allows you to add more information to the graph object by combining data from an external source, in this case a `data.frame` object. Here is an example with some built-in data. The option `stratum` indicates the name of the column that has the node labels in it (which are stored as `V(graph)$name`).
```{r}
data(baja)
summary(baja)
lopho <- decorate_graph( lopho, baja, stratum="Population")
lopho
```
Each vertex has seveal different types of data associated with it now. We will use this below.
### Plotting a graph using normal plotting methods
One of the main benefits to using R is that you can leverage the mutlitude of other packages to visualize and manipulate your data in interesting and informative ways. Since a `popgraph` is an instance of an **igraph**element, we can use the **igraph**routines for plotting. Here is an example.
```{r}
plot(g)
```
There are several different options you can use to manipulate the graphical forms. By default, the plotting routines look for node and edge attributes such as `name` and `color` to plot the output appropriately. There are several additional plotting functions for plotting **igraph** objects. Here are some examples.
```{r}
plot(g, edge.color="black", vertex.label.color="darkred", vertex.color="#cccccc", vertex.label.dist=1)
```
```{r}
layout <- layout.circle( g )
plot( g, layout=layout)
layout <- layout.fruchterman.reingold( g )
plot( g, layout=layout)
```
### Plotting a graph using **ggplot2** routines
The **ggplot2** package provides a spectacular plotting environment in an intuitive context and there are now some functions to support the Population Graphs in this context.
If you haven't used **ggplot2** before, it may at first be a bit odd because it deviates from normal plotting approaches where you just shove a bunch of arguments into a single plotting function. In **ggplot**, you build a graphic in the same way you build a regression equation. A regression equation has an intercept and potentially a bunch of independent terms. This is exactly how **ggplot** builds plots, by adding togther components.
To specifiy how things look in a plot, you need to specify an aesthetic using the `aes()` funciton. Here is where you supply the variable names you use for coordinate, coloring, shape, etc. For both of the `geom_*set` funcitons, these names **must** be attributes of either the node or edge sets in the graph itself.
Here is an example using the *Lopohcereus* graph. We begin by making a `ggplot()` object and then adding to it a `geom_` object. The 5**popgraph** package comes with two funcitons, one for edges and one for nodes.
```{r}
library(ggplot2)
p <- ggplot()
p <- p + geom_edgeset( aes(x=Longitude,y=Latitude), lopho )
p
```
I broke up the plotting into several lines to improve readability, it is not necessary to to this in practice though. The addition of additional `geom_` objects to the plot will layer them on top (n.b., I also passed the *size=4* option to the plot as the default point size is a bit too small and this is how you could change that).
```{r}
p <- p + geom_nodeset( aes(x=Longitude, y=Latitude), lopho, size=4)
p
```
And then you can add additional options to the plot, like axis labels and a less exciting background theme (the `theme_empty()` provided by **popgraph** is actually transparent so you can save the image and throw it into a presentation as necessary).
```{r}
p <- ggplot() + geom_edgeset( aes(x=Longitude,y=Latitude), lopho, color="darkgrey" )
p <- p + geom_nodeset( aes(x=Longitude, y=Latitude, color=Region, size=size), lopho)
p <- p + xlab("Longitude") + ylab("Latitude")
p + theme_empty()
```
You can also use the default layout routines in **igraph** for visualization. Here is an example using Fruchterman-Reingold algorithm.
```{r}
c <- layout.fruchterman.reingold( lopho )
V(lopho)$x <- c[,1]
V(lopho)$y <- c[,2]
p <- ggplot() + geom_edgeset( aes(x,y), lopho, color="darkgrey" )
p <- p + geom_nodeset( aes(x, y, color=Region, size=size), lopho)
p + theme_empty()
```
### Reading Existing **popgraph** Files
The online versions of Population Graphs provides a *.pgraph file for visualization. These files are visualized in several different software platforms including GeneticStudio (Dyer 2009), a OpenGL visualization application (Dyer & Nason 2004), an online visualization framework at http://dyerlab.org, and of course, in R. We shall focus on this last one. Reading in files to R
```{r,eval=FALSE,echo=TRUE}
graph <- read.popgraph( "thegraph.pgraph" )
```
### Saving Population Graph Objects
A **popgraph** object is a normal R object and can be saved using the normal R mechanisms.
```{r,echo=TRUE,eval=FALSE}
save( lopho, file="MyLophoGraph.rda")
```
For interoperability, **popgraph** objects can also be saved in other formats. These are accessed through the `write.popgraph()` function.
```{r,echo=TRUE,eval=FALSE}
write.popgraph(lopho,file="~/Desktop/Cactus.pgraph", format="pgraph")
```
There are several other options available for outputing your graph. Currently the other formats that have been implemented are:
1. `json` A format for html javascript data processing.
+ `kml` The Keyhole Markup Language which is read by GoogleEarth. This requires `Latitude` and `Longtitude` vertex properties.
+ `graphml` The graph markup language.
+ `html` Export as an interactive html document you can manipulate on your desktop (uses javascript d3.js library so you need an internet connection).
+ `pajek` Export to a format that works with the software Pajek (http://pajek.imfm.si/doku.php?id=pajek)
+ `pgraph` The format used in GeneticStudio and the original popgraph 3D viewer (this is the default).
+ `adjacency` Saves the adjacency matrix of the graph (binary) as a CSV file
+ `paths` Saves the shortest paths matrix as a CSV file
+ `weights` Saves the adjacency matrix with edge weights.
## Spatial Population Graphs
Mapping the nodes and edges onto real space is a key task in the understanding of how covariance is partitioned on the landscape. There are several approaches that can be used in R since it is such a flexible platform. In what follows I will use a series of techniques that I find useful ordered from the simplest to the more complicated.
### General Maps
For quick maps I typically use the **maps** library. It is pretty straightforward to use and does not take too much thought to quickly plot something or find the approporiate raster files. Below, I add some coordinates to the data set.
```{r}
V(g)$Latitude <- c( 47.15, 48.75,38.81, 42.26, 37.74 )
V(g)$Longitude <- c(-122.89,-122.49,-89.98, -93.47, -77.16 )
```
Then overlay this onto a map using the `overlay_popgraph()` function. Here is an example where I plot it over the map of the US states.
```{r,message=FALSE,fig.cap="Map of graph stetched onto continential US map."}
library(maps)
map( "state" )
overlay_popgraph(g)
```
This function **requires** that you already have a plot available (it uses the `lines()` and `points()` routines). If you try to just overlay this with no existing plot, it will not work (and should throw an error).
### Integrating Google and **ggplot2** for Plotting
R has some pretty good facilities for using spatial assests from Google and OpenStreetMaps and is a very easy way to get quick plots from Population Graphs, particularly if you can integrate it into the **ggplot2** framework.
Using the **ggmap** package, you can request map tiles and use as backgrounds onto which you can plot specific objects. To do so, you must first get:
- Either the centroid of the location you are interested in finding and a value of zoom (just like in google maps), or
- A bounding box with left, bottom, right, top coordinates. This is a bit of an experimental thing and does not always get you what you want.
Some fiddling is required with either way you go. The map you get from `get_map()` is essentially a huge matrix of hex colors as shown above.
```{r, message=FALSE}
library(ggmap)
location <- c( mean(V(lopho)$Longitude), mean(V(lopho)$Latitude))
location
map <- get_map(location,maptype="satellite", zoom=6)
dim(map)
map[1:4,1:4]
```
This map object can be passed on to `ggmap()`, which replaces the traditional `ggplot()` funciton and sets up the bounding box in terms of Latitude and Longtidue. Onto this, you can plot the graph topologoy using:
- `geom_edgeset()` This takes the graph and plots out the edges.
- `geom_nodeset()` This plots out the nodes. You could probably use a regular *data.frame* and `geom_point()` as well. Here is an example:
```{r}
p <- ggmap( map )
p <- p + geom_edgeset( aes(x=Longitude,y=Latitude), lopho, color="white" )
p <- p + geom_nodeset( aes(x=Longitude, y=Latitude, color=Region, size=size), lopho)
p + xlab("Longitude") + ylab("Latitude")
```
### Integrating Raster Maps
At times we have raster data upon we can plot a population graph. Here is an example from Baja California. The underlying raster image is croped from a WorldClim tile and represents elevation.
```{r,message=FALSE}
library(raster)
data(alt)
plot(alt)
```
Since it is a **raster** object, it knows how to plot itself relatively well. There are a ton of good references for showing you how to play with raster data (e.g., Bivand *et al.* 2008)
To plot our graph onto this topology, we export the spatial components of the graph into objects that interact with rasters. The packages provides simple extraction of features into `SpatialLines` and `SpatialPoints` objects.
```{r}
lopho.nodes <- to_SpatialPoints(lopho)
lopho.nodes
lopho.edges <- to_SpatialLines(lopho)
head(lopho.edges)
```
Once we have them extracted into the right format, we can add them to the raster plot. I plot the nodes twice to overlay a circular icon (`pch=16`) onto the default cross marker and make them 50 \% larger (`cex=1.5`).
```{r, fig.width=8}
plot( alt )
plot( lopho.edges, add=TRUE, col="#555555" )
plot( lopho.nodes, add=TRUE, col="black", cex=1.5 )
plot( lopho.nodes, add=TRUE, col=V(lopho)$color, pch=16, cex=1.5 )
```
## Extracting Spatial Data Using Population Graphs
Since we are dealing with the spatial stuff right now, it makes sense to look into how we can use the topological features of the graph to extract spatial data.
### Node Specific Data
The node data `nodes` is a `SpatialPoints` object and can be used to pull data from raster sources. I'll start by creating a `data.frame` with some existing data in it.
```{r}
df.nodes <- data.frame(Pop=V(lopho)$name, Latitude=V(lopho)$Latitude, Longitude=V(lopho)$Longitude)
```
Then we can extract the elevation from the `alt` raster as:
```{r}
library(raster)
df.nodes$Elevation <- extract( alt, lopho.nodes )
summary(df.nodes)
```
Additional data could be extracted from other rasters. See http://worldclim.org for some example data that may prove useful.
### Data Along Edges
```{r}
df.edge <- data.frame(Weight=E(lopho)$weight )
summary(df.edge)
```
For each of the `{r}length(E(lopho))` edges, we can exract the elevation profile. Here is an example from one of the edges.
```{r}
plot(alt)
plot(lopho.edges[3],add=TRUE,col="red",lwd=3)
```
Along this transect, you can pull out the elevation values as follows (n.b., the return object is a list so I'm only taking the first, and only, element in it).
```{r,message=FALSE}
library(ggplot2)
Elevation <- extract( alt, lopho.edges[3] )[[1]]
e <- extent( lopho.edges[3] )
e
Latitude <- seq(ymin(e),ymax(e),length.out=length(Elevation))
qplot( Latitude, Elevation, geom="line" )
```
There is a lot of ways that you can play with this functionality. This should get you going.
## Extracting Graph-Theoretic Parameters
The underlying structure of a **popgraph** object is based upon the **igraph** package from Gabor Csardi. A population graph is essentially a specific kind of **igraph**object and can be decorated with metadata that is useful for spatial population genetic analyses. As such, there is a wealth of existing analyses from both the **igraph** as well as the **sna** packages that can be used on **popgraph** objects. Here are some examples.
### Matrix Representations of Population Graph Objects
A graph topology is a graphical representation of a matrix and there are several reasons why you may want to use these matrices. The function `to_matrix()` is an easy front-end to several kinds of matrices. Matrix structure itself can be defined by adjacency matrics, either binary (the default) or weighed by the edge weight. Several graph-theoretic parameters are derived from the adjacency matrix. Here is an example from our little graph that started this document.
```{r}
to_matrix( g, mode="adjacency")
to_matrix( g, mode="edge weight")
```
In addition to who each node is connected to, it is often of interest to know the length of the shortest path through the matrix connecting nodes. Here is a slightly larger example, using the cactus data so we can look at isolation by graph distance.
```{r}
cGD <- to_matrix( lopho, mode="shortest path")
cGD[1:5,1:5]
```
It should be noted that the shortest distance through a population graph is defined as the parameter $cGD$, conditional graph distance (see Dyer *et al.* 2010 for more information on this parameter).
Now, we need the physical distance between the nodes. If the physical size of the sampling area is small we could just use the Pythagorean equation. However, here the distance is relatively large and the curvature of the earth may be of interest to take into account. There are seveal functions that will calculate 'great circle distance' but the easiest is `rdist.earth()` from the **fields** funtion.
```{r,warning=FALSE,message=FALSE}
library(fields)
pDist <- rdist.earth( cbind( V(lopho)$Longitude, V(lopho)$Latitude ) )
```
Now, we can plot these values against each other to see if there is a pattern of 'isolation by distance' captured in the graph topology. To do this, I extract only the upper triangle (e.g., the values above the diagonal of each matrix) because they are symmetric matrices and we do not want to look at each datum twice.
```{r}
df <- data.frame( cGD=cGD[upper.tri(cGD)], Phys=pDist[upper.tri(pDist)])
cor.test( df$Phys, df$cGD, method="spearman")
```
We can plot these values and make a trendline pretty easily. Here is a plot using **ggplot2** (a very nice plotting library; you could use `plot()` to do the normal plotting but I think **ggplot2** does such a nice job I encourage its use).
```{r,message=FALSE}
qplot( Phys, cGD, geom="point", data=df) + stat_smooth(method="loess") + xlab("Physical Distance") + ylab("Conditional Genetic Distance")
```
The trendline is the loess predicted line with confidence interval.
### Node Specific Parameters
Features of the topology can be extracted as either properties of the nodes or the edges. Node properties may provide insights into localized processes (e.g., a 'sink' population). There are a lot of different parameters that can be derived and several packages in R that help out. Here are some basic ones.
```{r}
df.nodes$closeness <- closeness(lopho)
df.nodes$betweenness <- betweenness(lopho)
df.nodes$degree <- degree( lopho )
df.nodes$eigenCent <- evcent( lopho )$vector
df.nodes$Region <- factor(V(lopho)$Region)
summary(df.nodes,color="Region")
```
The relationship betwwen the node variables can be evaluated in a pair plot.
```{r,message=FALSE,warning=FALSE,fig.width=8}
library(GGally)
ggpairs(df.nodes,columns=2:9, color='Region')
```
### Edge Specific Parameters
Edges may have specific properties as well. Here are some examples using betweeness centrality, community, and regionality (if the edge connects within Baja or Sonora or crosses the Sea of Cortéz).
```{r}
df.edge$betweenness <- edge.betweenness(lopho)
df.edge$Region <- rep("Baja",52)
df.edge$Region[36:52] <- "Sonora"
df.edge$Region[c(11,24,27,35)] <- "Cortez"
ggpairs(df.edge, color="Region")
```
## Testing for Topological Congruence
If you have more than one topology and the node sets overlap sufficiently, you can test for the topological congruence of the two. There are several specific reasons why population graph topologies may be congruent. In this section we use the spatial genetic structure of the Senita cactus (*Lophocereus schottii*) and its obligate pollinator, *Upiga virescens* as an example. This is an obligate pollination mutualism and as such we should expect there to be some degree of spatial genetic congruence between the species due to this co-evolution.
```{r}
data(upiga)
upiga <- decorate_graph(upiga,baja,stratum="Population")
upiga.nodes <- to_SpatialPoints(upiga)
upiga.edges <- to_SpatialLines(upiga)
```
These two data sets were 'mostly' collected in the same physical locations. Here is a comparison of the two topologies.
```{r,fig.width=9}
par(mfrow=c(1,2))
plot(lopho)
plot(upiga)
```
You can clearly see some differences in both the node and edge sets. However, given the relationship between these organisms, there is an expectation that they should share some spatial structure. The function `congruence_topology()` is designed to extract the congruence graph that is the intersection of both node and edge sets. If the node sets are not completely overlapping (in this case they are not), it will give you a warning. If you want to compare topologies, you must start with identical node sets because the topology in a Population Graph is based upon the entire structure, not just pairwise differences. See the writeup about the **gstudio** package for more information on this.
```{r,message=FALSE}
cong <- congruence_topology(lopho,upiga)
plot(cong)
```
We can then take the congruence graph and plot it or work with it in the normal fashion.
```{r}
cong <- decorate_graph( cong, baja )
cong.nodes <- to_SpatialPoints(cong)
cong.edges <- to_SpatialLines(cong)
plot(alt)
plot(cong.edges,add=T)
plot(cong.nodes,add=T, pch=16, col="red")
```
There are several ways to examine 'congruence' in graph topologies, of which I show two. The first method is based upon the correlation of pair-wise distance through the graph for each. That is to say, are proximate nodes in `lopho` similarily close in `upiga`? This is called "Distance Congruence" and is based upon a non-parametric correlation of path distances.
```{r,warning=FALSE}
test_congruence(lopho,upiga,method="distance")
```
Another way is to ask about the pattern of connectivity. Based upon the number of nodes and edges in `lopho` and `upiga`, are there more in the congruence graph than could be expected if the two graph were just randomly associated? This is called "Structural Congruence" and is determined combinatorially. What is returned by this is the probability having as many edges as observed in the congruence graph given the size of the edge sets in the predictor graphs. You can think of this as the fraction of the area under the line as large or larger than the observed.
```{r,warning=FALSE}
test_congruence(lopho,upiga, method="combinatorial")
```
Hope this provides enough of an overview of the **popgraph** package to get you started. If you have any questions feel free to email [mailto://[email protected]].