Bilateral symmetry is a fundamental feature of most organisms. This refers to the idea that some biological structure is repeated in a different position or orientation, meaning that the arrangement in space of some structures is at least partially redundant (Klingenberg et al., 2002). There are two types of bilateral symmetry that the reader should be aware of. The first is matching symmetry, where a symmetric structure is present in two separate copies found on the left and right side of the body (i.e. a mosquito’s wings). The second is object symmetry, where a single structure is symmetric itself along the midsagittal plane (i.e. a vertebrate skull). The approach to analyzing data with each of these types of symmetry is somewhat different.
The asymmetry of a structure can also be of interest, particularly in terms of the developmental origins of shape variation (Klingenberg et al., 2002). Asymmetry can manifest in a dataset as directional, whereby consistent difference is skewed toward one side, perhaps as a reflection of difference in function (i.e. crab claws). Asymmetry can also be fluctuating which reflects instability during development.
The geomorph function bilat.symmetry allows the user to
quantify components of shape variation as described by their patterns of
symmetry and asymmetry. In particular, the function decomposes shape
variation into variation among individual, variation among sides
(directional asymmetry), and variation due to the interaction between
individual and side (fluctuating asymmetry). Each of these components
are then evaluated using Procrustes ANOVA.
Simply accounting for symmetry in a sample can be extremely important as well. In a highly symmetrical structure like a cranium, one can run into issues like ill-conditioning of the covariance matrix. This function can be used to extract the symmetric components of shape variation to address this problem, but this method is explained in a different tutorial.
bilat.symmetry()
\(A\): A 3D array
of either raw, or Procrustes-aligned landmark coordinates OR a gpagen object, that is, the output from the
gpagen function.
\(ind\): A vector containing labels for each individual:
lizards$ind
## [1] 29 29 30 30 31 31 32 32 33 33 34 34 35 35 36 36 37 37 38 38 39 39 40 40 41 41 42 42 43 43 44 44 45 45 46
## [36] 46 47 47 48 48 49 49 50 50 51 51 52 52 53 53 54 54 55 55 56 56 57 57 58 58 59 59 60 60 61 61 62 62 63 63
## [71] 64 64 65 65 66 66 67 67 68 68 69 69 70 70 71 71 72 72 73 73 74 74 75 75 76 76 77 77
Here, the levels indicate different individuals, whose landmarks on
sided elements have been repeated.
mosquito$side
## [1] 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2
lizards$rep
## [1] 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1
## [54] 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2
\(object.sym\): Logical value (TRUE/FALSE) indicating whether analysis should be based on object symmetry (TRUE) or matching symmetry (FALSE).
\(land.pairs\): In the case of object symmetry, this argument contains a matrix indicating numbers for matched pairs of landmarks across the line of symmetry:
lizards$lm.pairs
## [,1] [,2]
## [1,] 13 14
## [2,] 2 11
## [3,] 15 16
## [4,] 27 28
## [5,] 3 10
## [6,] 17 19
## [7,] 4 9
## [8,] 20 21
## [9,] 22 24
## [10,] 5 8
## [11,] 6 7
## [12,] 25 26
\(data\): A geomorph data frame with relevant data for this analysis. Specifically, the data frame \(must\) include variables for “ind” “side” and “replicate” as shown above.
\(seed\): Optional argument for setting the seed for random permutations of the resampling procedure. If seed = “random,” P-values for statistical comparisons will vary when the function is run multiple times on the same data. If left NULL (the default) P-values will be the same for repeated runs of the function, as long as the number of iterations (iter) remains the same.
\(print.progress\): A logical value (TRUE/FALSE) to indicate whether a progress bar should be printed to the screen. This is helpful for long-running analyses.
Using a dataset of landmark coordinates taken from mosquito wings, below is an example of an analysis using this function on data with matching symmetry. In this case, the argument “object.sym” will be set to FALSE (as it is by default). The structure of the arguments for “ind,” “side,” and “replicate,” are illustrated above.
mosq.sym <- bilat.symmetry(mosquito$wingshape, ind = mosquito$ind, side = mosquito$side,
replicate = mosquito$replicate, object.sym = FALSE, print.progress = FALSE)
Results can be summarized using the base R function
summary
summary(mosq.sym)
##
## Call:
## bilat.symmetry(A = mosquito$wingshape, ind = mosquito$ind, side = mosquito$side,
## replicate = mosquito$replicate, object.sym = FALSE, print.progress = FALSE)
##
##
## Symmetry (data) type: Matching
##
## Type I (Sequential) Sums of Squares and Cross-products
## Randomized Residual Permutation Procedure Used
## 1000 Permutations
##
## Shape ANOVA
## Df SS MS Rsq F Z Pr(>F)
## ind 9 0.104888 0.0116542 0.45533 2.6901 1.53968 0.061 .
## side 1 0.003221 0.0032209 0.01398 0.7435 -0.47265 0.681
## ind:side 9 0.038990 0.0043323 0.16926 1.0407 0.27074 0.390
## ind:side:replicate 20 0.083259 0.0041629 0.36143
## Total 39 0.230358
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
##
## Centroid Size ANOVA
## Df SS MS Rsq F Z Pr(>F)
## ind 9 4.1497e-09 4.6107e-10 0.18555 0.8452 -0.21500 0.592
## side 1 3.4740e-10 3.4738e-10 0.01553 0.6368 0.22432 0.437
## ind:side 9 6.9569e-09 7.7299e-10 0.31108 1.4170 0.70241 0.237
## ind:side:replicate 20 1.0910e-08 5.4549e-10 0.48784
## Total 39 2.2364e-08
The summary output shows results for a shape ANOVA and Centroid size
ANOVA. For a more detailed explanation of the ANOVA output, see the
tutorial on Procrustes ANOVA. Additionally, results can
be plotted using the base R function plot:
plot(mosq.sym)

The top row of this figure shows the symmetric (left) and asymmetric
(right) components of our data. The bottom row illustrates, via TPS
diagram, the directional (left: variation among sides) and fluctuating
(right: variation due to an individual x side interaction) asymmetry of
our data.
When the argument “object.sym” is set to TRUE, the function performs an analysis of object symmetry. Remember in this case, that an object identifying landmark pairs is required.
scallop.sym <- bilat.symmetry(scallops$coorddata, ind = scallops$ind, object.sym = TRUE,
land.pairs = scallops$land.pairs, print.progress = FALSE)
Results of an analysis with object symmetry can likewise by
summarized using the base R function summary
summary(scallop.sym)
##
## Call:
## bilat.symmetry(A = scallops$coorddata, ind = scallops$ind, object.sym = TRUE,
## land.pairs = scallops$land.pairs, print.progress = FALSE)
##
##
## Symmetry (data) type: Object
##
## Type I (Sequential) Sums of Squares and Cross-products
## Randomized Residual Permutation Procedure Used
## 1000 Permutations
##
## Shape ANOVA
## Df SS MS Rsq F Z Pr(>F)
## ind 4 0.126060 0.031515 0.64135 9.8318 5.4476 0.001 **
## side 1 0.057671 0.057671 0.29341 17.9918 2.0350 0.011 *
## ind:side 4 0.012822 0.003205 0.06523
## Total 9 0.196552
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
The base R function plot returns a visualization of
directional and fluctuating asymmetry in our data. In this case, since
the data are three-dimensional, an interactive 3D plot is generated.
Within R, this will be opened in a separate window.
plot(scallop.sym)
This function generates an object of class “bilat.symmetry” which is a list, whose objects can be accessed separately using the $ operator:
bilat.symmetry() Output