library(knitr) opts_chunk$set(eval = TRUE, message=FALSE, warnings=FALSE, results="hold")
As of Apr. 2022, I'm looking for funding and thus time to finish MomX. Some of it below may become obsolete but it's quite often demanded so I brought it back to life.
As of Oct. 2018, Momocs is being extensively polished these days and may evolve before its 2.0 release as part of MomX.
Momocs aims to provide a complete and convenient toolkit for morphometrics. It is intended for scientists interested in describing quantitatively the shape, and its (co)variations, of the objects they study.
In the last decade, R has become the open-source lingua franca for statistics, and morphometrics known its so-called "revolution". Nevertheless, morphometric analyses still have to be carried out using various software packages either dedicated to a particular morphometric and/or for which source code is mostly unavailable and/or copyrighted. Moreover, most of existing software packages cannot be extended and their bugs are hard to detect and thus correct. This situation is detrimental to morphometrics: time is wasted, analyses are restricted to available methods, and last but not least, are poorly reproducible. This impedes collaborative effort both in software development and in morphometric studies.
By gathering the common morphometric approaches in an open-source environment and welcoming contributions, Momocs is an attempt to solve this twofold problem.
Momocs hinges on the core functions published in the must-have Morphometrics with R by Julien Claude (2008), but has been further extended to allow other shape description systems. So far, configurations of landmarks, outlines and open outline analyses, along with some facilities for traditional morphometrics are implemented.
Prior to analysis, Momocs can be used to acquire and manipulate data or to import/export from/to other formats. Momocs also has the facility for a wide range of multivariate analyses and production of the companion graphics. Thus a researcher will find that just a few lines of code will provide initial results, but the methods implemented can be finely tuned and extended according to the user's needs.
citation("Momocs")
.?efourier
.Momocs_help("efourier")
.bonhomme.vincent@gmail.com
.vignette("Momocs_FAQ")
.First, of all, let's download the last version of Momocs. You will need to install the devtools
package - if you do not have it yet - to get it from my GitHub repository :
install.packages("devtools") devtools::install_github("MomX/Momocs")
The typical install_packages("Momocs")
will get you the last CRAN version of Momocs, but the GitHub version is preferred as Momocs is still under active development.
We can start using Momocs, as long as it has been loaded using:
library(Momocs)
Future versions of Momocs (and MomX at some point) will totally embrace the tidyverse. My best advice if you're doing regular data science using R and if you do not master it yet is : i) to have a look at it https://www.tidyverse.org/ and ii) to have more than a look at it.
*One important point is you use tidyverse or any of its packages (dplyr, purrr, etc.) is to load them before Momocs, ie :
library(tidyverse) library(Momocs)
Keywords used all accross Momocs are introduced here in bold.
Morphometrics is the ugly job of turning beautiful shapes into quantitative variables. Just kidding, that's pretty exciting.
A shape is defined as a collection of (x; y)
coordinates. No 3D yet but different families are handled by Momocs:
shapes[18] %>% coo_sample(60) %>% coo_plot(points=TRUE) olea[11] %>% coo_sample(32) %>% coo_plot(points=TRUE) wings[1] %>% coo_plot(points=TRUE)
They are all single shapes defined by a matrix of (x; y)
coordinates; here are the first points of the moon:
shapes[18] %>% head()
Many operations on shapes are implemented such as: plotting, geometric operations, scalar descriptions, etc. These operations have their dedicated vignette, see vignette("Momocs_coo")
.
Working on single shape can quicly be boring. Shapes can be organized into a collection of coordinates: a Coo
object that carries:
$coo
, a list
of shapes (as matrix
.ces);$fac
, a data.frame
(or the modern equivalent, a data_frame
) to store covariates, either factor
s or numeric
s;One can do several things with a Coo
object: visualize it, apply morphometric operations, handle the data it contains, but in the end, a morphometric method will turn coordinates into coefficients.
Such morphometric operation on coordinates produce a collection of coefficients: a Coe
object that carries:
$coe
, a matrix
of coefficients;Coo
, $fac
is inherited;This can be summarized as follows:
Coo
| + | Morphometric method | = | Coe
---------------------------|----|----------------------|----|---------------
(x; y)
coordinates | + | appropriate method | = |quantitative variables
Coo
objects are collections of coordinates that become Coe
objects when an appropriate morphometric method is applied on them.
Some operations on Coo
/Coe
are generic in that they do not depend of the nature of the shape. For instance, centering a configuration of landmarks or an outline, or calculating their centroid size is, mathematically, the same generic operation. But some operations on shapes are specific to a peculiar family. For instance, calculating elliptical Fourier transforms on a configuration of landmarks would make no sense.
Momocs implement this desirable behavior and defines classes and subclasses, as S3 objects.
Coo
| Morphometrics methods | Coe
-------------------------------------|------------------------------------------------ |----------
OutCoo
(outlines) | efourier
, rfourier
, sfourier
, tfourier
| OutCoe
OpnCoo
(open outlines) | npoly
, opoly
, dfourier
| OpnCoe
LdkCoo
(configuration of landmarks)|fgProcrustes
, slide
| LdkCoe
In other words:
{OutCoo, OpnCoo, LdkCoo}
and is also a Coo
object;{OutCoe, OpnCoe, LdkCoe}
and is also a Coe
object.Finally, generic and specific operations can be applied to the Coe
objects, chiefly multivariate methods, capitalicized: PCA
, LDA
, CLUST
, MANOVA
(and MANCOVA), MSHAPES
, KMEANS
, etc.
Overall, Momocs implements a simple and consistent grammar that is detailed below.
Also, if you're familiar with modern R and the Hadley-verse, you should feel home as ggplot2
graphics, dplyr
verbs and magrittr
pipes are implemented.
Let's load one of the Momocs datasets, some various outlines (an Out
object):
shapes # prints a brief summary panel(shapes, names=TRUE) # base graphics
shapes
is one of the datasets bundled with Momocs. It's ("lazy") loaded in memory as soon as you call it, no need for data(shapes)
. To see all Momocs' datasets, try data(package="Momocs")
. These datasets are all Coo
objects (try class(bot)
), ie collection of shapes.
One can do many things on a Coo
object, as above, eg printing a summary of it (just by typing its name in the console), plotting a family picture with panel
, etc.
So far, we're interested in single shapes so let's extract the 4th shape from shapes
, using the traditional syntax. We plot it with coo_plot
that comes with several options for plotting all families of shapes.
shp <- shapes[4] coo_plot(shp) # coo_plot is the base plotter for shapes # but it can be finely customized, see ?coo_plot coo_plot(shp, col="grey80", border=NA, centroid=FALSE, main="Meow")
Let's now do some basic operations on this shape. They all named coo_*
and you can have the full list with apropos("coo_")
. coo_*
family encompasses:
coo_plot(coo_center(shp), main="centered Meow") coo_plot(coo_sample(shp, 64), points=TRUE, pch=20, main="64-pts Meow")
Momocs is fully compatible with maggritr's pipe operators. A nice introduction can be found there. magrittr
requires a (very small) cerebral gymnastics at the beginning but the benefits are huge, for defining moprhometric pipelines in Momocs but also for R as a whole. It makes things clearer, it: saves typing; reduces intermediate variable assignation; reads from left to right; substantiates the pipe we (should) have in mind. magrittr
's pipes are already loaded with Momocs.
shapes[4] %>% coo_smooth(5) %>% coo_sample(64) %>% coo_scale() %>% coo_plot() # pipes can be turned into custom function cs64 <- function(x) x %>% coo_sample(64) %>% coo_scale() %>% coo_center() shapes[4] %>% cs64 %>% coo_plot() # note the axes
Have a look to the dedicated vignette with vignette("Momocs_coo")
.
The most familiar operation can directly be applied on Coo
objects:
bot %>% coo_center %>% coo_scale %>% coo_alignxax() %>% coo_slidedirection("up") %T>% print() %>% stack()
A word about data import: you can extract outlines from a list of black masks over a white background, as .jpg
images with import_jpg
. Have a look to helpfiles (import_jpg
and import_jpg1
) for more details. Here we do not bother with import since we will use the bot
tles outlines dataset bundled with Momocs.
data(bot) bot panel(bot, fac="type", names=TRUE) stack(bot)
Here, we will illustrate outline analysis with elliptical Fourier transforms (but the less used and tested rfourier
, sfourier
and tfourier
are also implemented).
The idea behind elliptical Fourier transforms is to fit the x
and y
coordinates separately, that is the blue and red curves below:
coo_oscillo(bot[1], "efourier")
Graphically, this is equivalent to fitting Ptolemaic ellipses on the plane, try the following:
Ptolemy(bot[1])
Let's calibrate the number of harmonics required. More details can be found in their respective help files. Try the following:
calibrate_harmonicpower_efourier(bot) calibrate_deviations_efourier(bot) calibrate_reconstructions_efourier(bot) ```` Here, 10 harmonics gather 99% of the harmonic power. If you're happy with this criterium, you can even omit `nb.h` in `efourier`: that's the default parameter, returned with a message. ```r bot.f <- efourier(bot, nb.h=10) bot.f
bot.f
is a Coe
object (and even an OutCoe
), you have have a look to the help files to go deeper into Momocs classes.
You can have a look to the amplitude of fitted coefficients with:
boxplot(bot.f, drop=1)
Now, we can calculate a PCA on the Coe
object and plot it, along with morphospaces, calculated on the fly.
bot.p <- PCA(bot.f) class(bot.p) # a PCA object, let's plot it plot_PCA(bot.p)
Amazing but we will do much better afterwards.
The question of normalization in elliptical Fourier transforms is central: have a look to ?efourier
.
You can also drop some harmonics with rm_harm
. And methods that removes the bilateral (a)symmetry are implemented: rm_asym
and rm_sym
, while symmetry
calculates some related indices.
Open outlines are curves. Methods actually implemented are:
npoly
that fit natural polynomials;opoly
that fit orthogonal (also called Legendre's) polynomials;dfourier
for the discrete cosine transform.Note that opoly
and npoly
can only be used on simple curves, curves that have at most one y
for any x
coordinates, at least under a given orientation. dfourier
can fit complex curves, curves "that back on their feets".
Here, we will work on the fertile valves of olive stones, a (very partial) dataset provided by my colleagues Terral, Ivorra, and others.
They have two orthogonal views (a lateral and a dorsal view). See the paper cited in ?olea
for more details. Let's explore it a bit:
olea pile(olea, ~view) # a family picture colored by a factor
Now, we gonna calculate opoly
on it and plot the result of the PCA. Notice how consistent is the grammar and the objects obtained:
op <- opoly(olea) # orthogonal polynomials class(op) # an OpnCoe, but also a Coe op.p <- PCA(op) # we calculate a PCA on it class(op.p) # a PCA object op %>% PCA %>% plot(~domes+var) # notice the formula interface to combine factors
But this is perfectly wrong! We merged the two views are if they were different individuals. Momocs can first chop
or filter
the whole dataset to separate the two views, do morphometrics on them, and combine
them afterwards.
with(olea$fac, table(view, var)) # we drop 'Cypre' since there is no VL for 'Cypre' var olea %>% filter(var != "Cypre") %>% # split, do morphometrics, combine chop(~view) %>% opoly %>% combine() %T>% # we print the OpnCoe object, then resume to the pipe print() %>% # note the two views in the morphospace PCA() %>% plot_PCA(~var)
Now the PCA is done on the combination of two OpnCoe
objects, each one resulting from an independant opoly
call. That is the meaning of the [ combined: opoly + opoly analyses ]
printed by the pipe above. Momocs can combine up to four different views.
_Landmarks methods are still quite experimental (i.e. not tested extensively)
Let's have a look to graphics facilities and apply a full generalized Procrustes adjustment:
pile(wings) options(Momocs_verbose=FALSE) # to silent Momocs w.al <- fgProcrustes(wings) pile(w.al) # PCA PCA(w.al) %>% plot_PCA(1)
Sliding landmarks are supported and rely on geomorph
package by Adams and colleagues.
pile(chaff) chaff.al <- fgsProcrustes(chaff) pile(chaff.al) chaff.al %>% PCA() %>% plot_PCA(~taxa, chullfilled = TRUE)
Again, the grammar is consistent for landmarks.
Traditional morphometrics lose geometries: from the variables, you can't unambiguously reconstruct the shape. Every shape is described by a combination of measurements, (inter landmark distance, quantitative variables, scalar descriptor, etc.)
Momocs provides some basics utilities to work with such objects in the TraCoe
class. There is not TraCoo
per se but it can be obtained from any Coo
with the measure
method. Let's take the hearts
dataset that comes from handdrawn heart shapes from my former colleagues at the French Intitute of Pondicherry:
hearts panel(hearts, fac="aut", names="aut")
Notice that there are 4 landmarks defined on them. Such landmarks on outlines can be: defined withdef_ldk()
, retrieved with get_ldk()
, and overall used to align outlines with fgProcrustes()
. You can compare: hearts %>% stack()
with hearts %>% fgProcrustes() %>% coo_slide(ldk=1) %>% stack()
.
Let's describe these hearts with scalar descriptors: area, circularity and the distance between the 1st and the 3rd bumps of the hearts. measure
is of great help. Note the loadings.
ht <- measure(hearts, coo_area, coo_circularity, d(1, 3)) class(ht) ht$coe ht %>% PCA() %>% plot_PCA(~aut)
Again, there are plenty of scalar descriptors of shape, which names starts with coo_*
, apropos("coo_")
. Have a look to the coo_vignette, see vignette("Momocs_coo")
.
Such a TraCoe
is provided in the flower
dataset which is simply a rearranged iris
. Once again, note the grammar consistency.
flower flower %>% PCA() %>% plot_PCA(~sp)
You can build your own TraCoe
with coo_scalars
:
bot_sc <- bot %>% coo_scalars %>% TraCoe(fac=bot$fac) bot_sc %>% PCA %>% plot_PCA(~type)
Note that, by default, PCA on TraCoe
object first centers and scales variables. This can be changed, see ?PCA
.
This section will mainly be illustrated with bot
, and consequently outline analysis, but it works exactly the same on any Coe
object, resulting from open outlines, configuration of landmarks, traditional morphometrics, etc.
bot.f <- efourier(bot)
PCA
: Principal Component AnalysisLet's see the main components of shape variability with a Principal Component Analysis.
bot.p <- PCA(bot.f) plot_PCA(bot.p)
Morphological spaces are reconstructed on the fly with plot.PCA
. We call it plot.PCA
because it uses the familiar plot
but on the particular PCA
class (type class(bot.p)
). We may want to display the two groups saved in bot$fac
. Just type the id of the column or its name.
plot_PCA(bot.p, ~type) # there are many ways to pass the factor, see ?plot_PCA and ?fac_dispatcher
See ?plot_PCA
for much more complex graphics. And also these helper functions for the PCA
class:
scree(bot.p) scree_plot(bot.p) boxplot(bot.p, 1) PCcontrib(bot.p, nax = 1:3)
You can also export the PCA
object as a .txt
file (see ?export
) or as a data_frame
for further use with R with:
bot.p %>% as_df(3) # The first three PCs
By the way, you can use Momocs plotters to plot non-morphometric datasets. Using a TraCoe
object is an option, but PCA
also works fine. Let's see an example with iris
dataset:
TraCoe(iris[, -5], fac=data.frame(sp=iris$Species)) %>% PCA() %>% plot_PCA(~sp)
LDA
: Linear Discriminant AnalysisWe can also calculate a Linear Discriminant Analysis on the PCA scores, or on the Coe
object, directly on the matrix of coefficients (and results may be better yet we may encounter collinearity between variables). Try the following:
#LDA(bot.f, 1) # we work on PCA scores bot.l <- LDA(bot.p, 1) # print a summary, along with the leave-one-out cross-validation table. bot.l # a much more detailed summary bot.l %>% summary # plot.LDA works pretty much with the same grammar as plot.PCA # here we only have one LD plot(bot.l) # plot the cross-validation table plot_CV(bot.l) # tabular version
You can also export turn it into a data_frame
with as_df
.
MANOVA
: Multivariate Analysis of (co)variaceWe can test for a difference in the distribution of PC scores with:
MANOVA(bot.p, ~type)
We can also calculate pairwise combination between every levels of a fac
. Here we just have two levels, so a single pairwise combination but the syntax is:
MANOVA_PW(bot.p, ~type)
If we want a MANCOVA instead :
bot %<>% mutate(cs=coo_centsize(.)) bot %>% efourier %>% PCA %>% MANOVA(~cs)
CLUST
: Hierarchical clusteringA hierarchical classification now. It relies on dist
+ hclust
+ ape::plot.phylo
.
CLUST(bot.p, ~type)
Monophyly is plotted by default. Many options can be found in ?CLUST
KMEANS
: K-means clusteringA very minimal k-means clustering is implemented:
KMEANS(bot.p, centers = 5)
MSHAPES
: Mean shapesWe can retrieve the mean shapes, group wise (if a fac
is specified), or the global mean shape (if omitted). It works from the Coe
object:
# mean shape bot.f %>% MSHAPES() %>% coo_plot() # mean shape, per group bot.ms <- MSHAPES(bot.f, ~type) # lets rebuild an Out Out(bot.ms$shp) %>% panel(names=TRUE) # or individual shapes beer <- bot.ms$shp$beer %>% coo_plot(border="blue") whisky <- bot.ms$shp$whisky %>% coo_draw(border="red")
We can also plot a pairwise comparison of them:
leaves <- shapes %>% slice(grep("leaf", names(shapes))) %$% coo leaves %>% plot_MSHAPES() # or from mshapes directly bot %>% efourier(6) %>% MSHAPES(~type) %>% plot_MSHAPES()
One common yet boring task of morphometrics consists in handling datasets: add new information, remove some individuals, etc.
Momocs adapts dplyr verbs to its objects, and add new ones. If you have never heard of dplyr, let's have a look to its introduction there, this may change your (R) life.
data(olea)
olea
mutate
: add new columnsmutate(olea, fake=factor(rep(letters[1:2], each=105)))
slice
: select individuals based on their positionslice(olea, 1:5) slice(olea, -(1:100))
filter
: select individual based on a logical conditionfilter(olea, domes=="cult") # %in% is useful filter(olea, var %in% c("Aglan", "Cypre")) # or its complement filter(olea, !(var %in% c("Aglan", "Cypre"))) # Also works with more than one condition filter(olea, domes=="cult", view!="VD") # or on operation on numeric, here a dummy numeric column olea %>% mutate(foo=1:210) %>% filter(foo<12) olea %>% mutate(foo=1:210) %>% filter(foo>median(foo))
select
: pick, reorder columns from the $fac# reorder columns select(olea, view, domes, var, ind) # drop some and show the use of numeric index select(olea, 1, Ind=ind) # drop one select(olea, -ind)
And you can pipe those operations: say, we only want dorsal views from domesticated individuals, for a (renamed) 'status' column, and drop the 'ind' column:
olea %>% filter(domes=="cult", view=="VD") %>% rename(domesticated=domes) %>% select(-ind)
You can also use dplyr verbs on the fac
directly (if you load dplyr before Momocs, you will not need 'dplyr::') eg:
olea$fac %>% dplyr::group_by(var) %>% dplyr::mutate(n=1:dplyr::n(), N=dplyr::n())
If you want to save it, do not forget to reassign it back to the $fac
: olea$fac <- olea$fac %>% some_operations
or even olea$fac %<>% some_operations
.
Note that if you due to namespace conflicts, if you use dplyr
, and in a larger extent the tidyverse you must use library(dplyr)
(or library(tydyverse)
) before library(Momocs)
.
That being said, the adaptation of these dplyr verbs should save time and some headaches.
New verbs are implemented: for instance, you can chop
(a split
on Coo
objects) according to a condition: this will create a list, on which you can apply further operations, then combine
it back. This is particularly useful when you want to apply independant treatments to different partitions, eg orthogonal views of your model. Prior to this, we can use table
to cross-tabulate data from $fac
. We could have done the first step of what follows with rm_uncomplete
that drops (if any) missing data.
with(olea$fac, table(var, view)) # we drop 'Cypre' since there is no VL for 'Cypre' var olea %>% filter(var != "Cypre") %>% # split, do morphometrics, combine chop(~view) %>% opoly %>% combine() %>% # note the two views in the morphospace PCA() %>% plot_PCA(~var)
Some methods help, on Coe
objects to:
* select groups with at least a certain number of individuals in them: at_least
removes outliers : which_out
sample a given number: sample_n
;
sample a given proportion: sample_frac
;
generate new individuals based on calibrated Gaussian coefficient generation: breed
;
* generate new individuals based on permutations: perm
.
Several shortcuts are implemented on Coo
and Coe
objects:
* names
returns shape names;
length
returns their number;
Ntable
does the same job and plots a confusion matrix;
[]
extracts one (or more) shape;
$
can access either a shape name or a column name for the $fac
.
Try the following:
names(bot) length(bot) bot[1] bot[1:5] bot$brahma bot$type
There are various morphometrics formats in the wild, almost as much as softwares. Momocs tries to create bridges between them, all gathered in the Babel family.
Note that these will move to Momit/Momecs asap.
You can convert from/to array, matrix, list or data.frame with the functions {a, m, l, d}2{a, m, l, d}
. For instance, l2a
converts a list into an array that you can use with geomorph
; a2l
does the inverse operation.
Imagine you want to import pupfish from geomorph
as a Ldk
object:
library(geomorph) data(pupfish) str(pupfish) # so $coords will become $coo, and # all other components will be turned into a data.frame to feed $fac # with a single line Ldk(coo=pupfish$coords %>% a2l, fac=pupfish[-1] %>% as.data.frame())
If you use StereoMorph to digitize landmarks and curves, you can import them, from the files produced with the functions import_StereoMorph_ldk
and import_StereoMorph_curve
.
.tps
files can be read with import_tps
.nts
files an be read with nts2Coo
(will be turned into import_nts
soon)You're not bound with Momocs from the "shapes" step, ie you do not have to start from Coo
objects. For instance if you have a matrix of coefficients, you can directly build an OutCoe
with the builder (see below). Same approach for OpnCoe
and TraCoe
; have a look to the help files of these builders.
# we simulate an imported matrix of coordinates, eg from a .csv coeffs_from_the_wild <- bot %>% efourier(6) %$% coe coeffs_in_Momocs <- OutCoe(coe=coeffs_from_the_wild, method="efourier", norm=TRUE) coeffs_in_Momocs %>% PCA %>% plot
spA_group7_ind4_VL.{txt|jpg|etc.}
, use lf_structure
;$fac
, rw_rule
is your friend;rescale
;.txt
(eg coordinates of landmarks on them), see tie_jpg_txt
.The best way to save a Momocs object is probably to use the base save
function. You can call it back afterwards with load
:
save(bot, file="Bottles.rda") # closing R, going to the beach, back at work load("Bottles.rda") # bot is back
Any Momocs object, Coo
s, Coe
s, PCA
s, etc. can be turned into a data.frame
with as_df
.
bot %>% as_df bot %>% efourier %>% as_df bot %>% efourier %>% PCA %>% as_df
If the heretic you want to exit R to do stats elsewhere, export
is your friend:
bot %>% efourier %>% export bot %>% efourier %>% PCA %>% export
But, of course, you can directly access information within the Momocs objects; try the following:
# from Coo objects bot$coo # list of matrices (of xy coordinates) bot$fac # data.frame for covariates # from Coe objects bot.f$coe # matrix of coefficients bot.f$fac # data.frame for covariates # from PCA objects bot.p$x # scores bot.p$rotation # rotation matrix
Most graphics are currently being rewritten, either to pure ggplot2
or using grindr. See the dedicated embryo of vignette: see vignette("Momocs_grindr")
.
tps_*
: Thin Plate SplinesTPS have not been presented before but here there are:
tps_grid(beer, whisky) tps_arr(beer, whisky) tps_iso(beer, whisky)
Again, plenty options in ?tps_*
.
You may also like lolliplots and friends:
coo_lolli(beer, whisky); title("coo_lolli") coo_arrows(beer, whisky); title("coo_arrow") # an example with coo_ruban coo_plot(beer) # to get the first plot coo_ruban(beer, edm(beer, whisky), lwd=8) # we add ruban based from deviations coo_draw(whisky) title("coo_ruban")
?palettes
.See vignette("Momocs_FAQ")
.
<bonhomme.vincent@gmail.com>
.Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.