0

I am doing work a geometric morphometric dataset of biological specimens with two dimensional Cartesian coordinates (i.e., landmarks). Part of my interest with this dataset is to produce a data frame of distances between important landmarks and angles between landmark vectors with homologous coordinates across all specimens for further comparison. Distances between points have not been a problem, but I have been running into problems when I have tried to calculate angles in each of my specimens.

I am trying to find a way to calculate the angle between line vectors representing the same angle on a series of specimens at once (the vectors describe a feature with biological significance that is present in all specimens across my dataset). The R package I use to manipulate the morphometric data outputs the landmarks as a three-dimensional object of class array() where rows are landmarks, columns are the dimensions of the coordinate, and the third dimension is the specimen. Here are the landmark coordinates for the three specimens in the sample data presented here.

, , Specimen1

         X         Y
1  -0.2411  0.060183
2  -0.0677 -0.029954
3   0.1147  0.012111
4   0.0085  0.000462

, , Specimen2

          X       Y
1  -0.22509  0.0764
2  -0.09437 -0.0202
3   0.09135 -0.0182
4   0.00367 -0.0045

, , Specimen3

         X        Y
1  -0.2223  0.06122
2  -0.1001 -0.02366
3   0.0553  0.00577
4  -0.0617 -0.01557

Below is a readable array that replicates the data I get as output for the first three specimens.

data<-array(data=c(-0.2411,-0.0677,0.1147,0.0085,0.060183,-0.029954,0.012111,0.000462,-0.22509,-0.09437,0.09135,0.00367,0.0764,-0.0202,-0.0182,-0.0045,-0.2223,-0.1001,0.0553,-0.0617,0.06122,-0.02366,0.00577,-0.01557),c(4,2,3))

I am interested in finding the angle formed by the lines between points 1 and 2 and points 3 and 4. To do this I calculate the vector of these two lines by subtracting the coordinates of one point from the other. When I do that I get a 2*N matrix for each vector where rows correspond to vectors in the X and Y axis and columns correspond to specimen.

I want to find the angle between these two vectors for specimen 1 (column 1 on both matrices), 2, 3, and so on. Ideally, I would like to get the output into a format where R reports the angle for each indivdual specimen, like so:

     Angle1
[,1] 146
[,2] 152
[,3] 135

And then somehow add it into a data frame so it looks like this:

          Distance Angle1 Angle2
Specimen1 100      146    100
Specimen2 100      152    100
Specimen3 100      135    100

The distances and values for Angle2 are just filler numbers to show the format I am trying to achieve.

I have tried using the angle() function in the mathlib package and the angle.calc() function in the Morpho package. Here is the code I used to calculate the angle using the angle() function.

vector1<-data[1,,]-data[2,,]
vector2<-data[3,,]-data[4,,]
angle(as.vector(vector1),as.vector(vector2),degree=TRUE)

These functions will calculate what seems to be the accurate angle however they will only do it for the first specimen or a single specimen if otherwise specified, and do not produce a list of angles for all specimens. I have also checked previous questions on Stack Overflow but none of the answers given appear to be applicable to a dataset containing multiple specimens with each column on both matrices representing a distinct specimen (and when I apply them at best they seem to have the same problem where they only return the angle for the first specimen). There are only three specimens here, I have a large number of specimens and multiple angles per specimen in the actual dataset and it is not possible to measure every angle individually for each specimen.

Is there any way to write a formula to produce a list containing the respective angle for each of these specimens?

user2352714
  • 314
  • 1
  • 15
  • Can you update your example to show how you'd want to handle "multiple angles per specimen"? Solving for just one comparison per specimen (which is what your expected output does now) is simpler than solving for an arbitrary number of comparisons. – andrew_reece Feb 19 '20 at 02:43
  • @andrew_reece Example is updated. In general I was thinking of trying to have a column for each angle and writing a separate equation for each one. Each intended angle column represent a certain feature that is measurable on each specimen that is of interest in the field of study. – user2352714 Feb 19 '20 at 05:07
  • Thanks for updating. Please have a look at the answers and respond in their respective comment feeds to let us know if we're missing anything. – andrew_reece Feb 19 '20 at 05:15

2 Answers2

2

First off, it's a bit confusing to give a 2x3 matrix the name vector1. That aside, you can use matlib::angle to calculate angles.

library(matlib)
phi <- setNames(mapply(
    function(x1, x2) angle(x1, x2),
    as.data.frame(vector1), as.data.frame(vector2)),
    paste0("Specimen", 1:ncol(vector1)))
phi
#Specimen1 Specimen2 Specimen3
# 146.2674  152.4439  134.8753

Explanation: Use mapply to simultaneously iterate over the columns of vector1 and vector2, which have been converted to data.frames. Use stack to convert the named vector into a data.frame:

stack(phi)
#    values       ind
#1 146.2674 Specimen1
#2 152.4439 Specimen2
#3 134.8753 Specimen3
Maurits Evers
  • 49,617
  • 4
  • 47
  • 68
  • I updated the question to give an idea of why the matrix is in a 2x3 format. Basically it's an x,y vector that results from subtracting two points from one another, and each column represents an individual specimen. – user2352714 Feb 19 '20 at 19:31
  • @user2352714 It would make more sense (from an R structures point-of-view) to store those vectors in a `list`. Not only do `vector`s then relate to vectors; it's then also easy to iterate over `list` elements using functions from the `*apply` family. Following your edit, I'm unclear now whether this solved your question or not. I'm showing you how to create a `matrix` of angles for every specimen. Does that address your issue? – Maurits Evers Feb 20 '20 at 00:07
  • I'm honestly not even sure what the best way to create the vectors is in the first place. I am not very handy with R. I am trying to find the angle between the lines formed by points 1-2 and 3-4 for all specimens. The angle functions say they need the information in vector format but don't show how to get that from Cartesian data, the best I could find is it's the difference in x and y between the two landmarks. It is not clear if the function wants linear vectors or an object of class `vector` or what. – user2352714 Feb 20 '20 at 01:39
  • @user2352714 A vector in R is a basic data structure containing elements of the same type. `c(1.0, 10, 20)` is a `numeric` vector; `c("A", "O", "L")` is a `character` vector; and so on. The function `angle` requires a `numeric` vector, see the examples given in the documentation `?angle`. – Maurits Evers Feb 20 '20 at 01:48
0

In general you may find it simpler to first get your data in a long/tidy format. That allows for easier grouping and aggregation, which is where vectorized/mapped operations can really shine.

Here's one way to do that with the data you provided:

library(matlib)
library(tidyverse)

n_specimen <- ncol(vector1)

data.frame(t(vector1)) %>%
  rename_all(~str_replace(., "X", "v1_")) %>%
  bind_cols(
    data.frame(t(vector2)) %>%
      rename_all(~str_replace(., "X", "v2_"))
    ) %>%
  mutate(specimen = paste0("Specimen", 1:n_specimen)) %>%
  pivot_longer(-specimen, names_to = "vector", values_to = "value") %>%
  separate(vector, into = c("vector", "ix"), sep = "_", remove = TRUE) %>%
  group_by(specimen) %>%
  summarise(angle = angle(value[vector == "v1"], value[vector == "v2"])) 

  specimen  angle
  <chr>     <dbl>
1 Specimen1  146.
2 Specimen2  152.
3 Specimen3  135.

By way of explanation, here's how the data frame looks after the separate step:

# A tibble: 12 x 4
   specimen  vector ix      value
   <chr>     <chr>  <chr>   <dbl>
 1 Specimen1 v1     1     -0.173 
 2 Specimen1 v1     2      0.0901
 3 Specimen1 v2     1      0.106 
 4 Specimen1 v2     2      0.0116
 5 Specimen2 v1     1     -0.131 
 6 Specimen2 v1     2      0.0966
 7 Specimen2 v2     1      0.0877
 8 Specimen2 v2     2     -0.0137
 9 Specimen3 v1     1     -0.122 
10 Specimen3 v1     2      0.0849
11 Specimen3 v2     1      0.117 
12 Specimen3 v2     2      0.0213

Each column has a unique level of information, and you can now group and summarise to the level of specificity required.

andrew_reece
  • 20,390
  • 3
  • 33
  • 58