# 6 mobest - Spatiotemporal Ancestry Interpolation and Search

## 6.1 Overview

mobest is an R package providing types and functions for spatiotemporal interpolation of human genetic ancestry components, probabilistic similarity search and the calculation of a derived measure of ancestry relocation and mobility. The workflow in mobest version 1.0.0 was specifically developed for Schmid and Schiffels (2023).

mobest assumes you have a set of genetic samples with spatial (two coordinates in a projected reference system) and temporal positions (years BC/AD) for which you calculated a derived, numeric measure of genetic ancestry (e.g. coordinates in a PCA or MDS space).

The package then provides functions to perform spatiotemporal interpolation using Gaussian process regression (GPR, “kriging”) with the laGP R package (Gramacy (2016)) to reconstruct an ancestry field based on the ancestry measure you provided.

*Journal of Statistical Software*72 (1): 1–46. https://doi.org/10.18637/jss.v072.i01.

mobest finally allows to derive a similarity likelihood for samples of interest within the interpolated field, which – under certain circumstances – can be interpreted as an origin probability.

The *Stuttgart* sample used for illustrative purposes here was taken from an Early Neolithic individual from Southern Germany and first published in Lazaridis et al. (2014).

## 6.2 Installing mobest

mobest is an R package and can be installed directly from GitHub with the following code on the R console:

```
if(!require('remotes')) install.packages('remotes')
::install_github('nevrome/mobest') remotes
```

You can also install specific/older versions of mobest with the following syntax: `nevrome/mobest[@ref|#pull|@*release]`

. For example to install the publication release version you can run `remotes::install_github('nevrome/mobest@1.0.0')`

.

For any of this to work a number of system libraries (mostly for processing geospatial data) have to be installed on your system, primarily for one particular dependency of mobest: the `sf`

R package (Pebesma (2018)). The following table includes the libraries and the names of the relevant packages in the package management systems of various Linux distributions and MacOS.

*The R Journal*10 (1): 439–46. https://doi.org/10.32614/RJ-2018-009.

System library | deb package (Ubuntu/Debian) |
rpm package (Fedora/CentOS) |
pkgbuild package (Arch) |
brew package (MacOS) |
---|---|---|---|---|

GDAL | libgdal-dev | gdal | gdal | gdal |

GEOS | libgeos-dev libgeos++-dev |
geos-devel | geos | geos |

PROJ | libproj-dev | proj-devel sqlite-devel |
proj | proj |

UDUNITS-2 | libudunits2-dev | udunits | udunits | udunits |

The `sf`

package maintainers provide a good explanation how to install these: https://r-spatial.github.io/sf/#installing

## 6.3 A basic similarity search workflow

This section explains the setup for a basic ancestry similarity search with mobest in R.

For didactic purposes we use a simplified version of the data and code generated for the publication that introduced mobest: Schmid and Schiffels (2023) . This is a fairly generic setup you can adjust to the needs of other and more specific projects.

The script explained in the following sections as well as the data required for it can be downloaded in its entirety here:

### 6.3.1 Preparing the computational environment

For this script we use various packages beyond base R, among which the following ones are required:

`readr`

for loading .csv input data`magrittr`

for the pipe operator`%>%`

`sf`

for loading and manipulating spatial data`rnaturalearth`

for downloading spatial reference data (Massicotte and South (2024))`ggplot2`

(and`cowplot`

) to visualize intermediate and final results (Wilke (2024))`dplyr`

for data manipulation of`data.frame`

s`mobest`

(obviously)

*Rnaturalearth: World Map Data from Natural Earth*. https://docs.ropensci.org/rnaturalearth/.

*Cowplot: Streamlined Plot Theme and Plot Annotations for ’Ggplot2’*. https://wilkelab.org/cowplot/.

*Journal of Open Source Software*4 (43): 1686. https://doi.org/10.21105/joss.01686.

`readr`

, `magrittr`

, `ggplot2`

and `dplyr`

are all in the tidyverse (Wickham et al. (2019)) and can be installed in one go with `install.packages("tidyverse")`

on the R console. For the installation of `sf`

and `mobest`

please see the instructions above.

We will generally call functions explicitly with their namespace using `::`

(so e.g. `readr::read_csv()`

). The only exceptions are `magrittr`

and `ggplot2`

, because we will use their functions so often that it becomes tedious to type them out. Instead we load them at the beginning.

```
library(magrittr)
library(ggplot2)
```

### 6.3.2 Preparing the input data

#### 6.3.2.1 Generating the the spatial prediction grid

mobest’s similarity search is typically run for a regular grid of spatial positions in the area of interest. It provides a function, `mobest::create_prediction_grid()`

, to create such a grid, given a specification of the desired area. This area is typically the land area in a certain part of planet Earth.

##### 6.3.2.1.1 Defining the research area

In a first step we therefore have to define the research area for our analysis as a polygon in space. One way of doing so is to provide a list of latitude and longitude coordinates (extracted e.g. from Google Maps). The following code defines a simple research area covering large parts of Western Eurasia.

```
<- sf::st_polygon(
research_area_4326 list(
cbind(
c(35.91,11.73,-11.74,-15.47,
37.06,49.26,49.56,35.91), # longitudes
c(25.61,28.94,31.77, 62.73,
65.67,44.56,28.55,25.61) # latitudes
)
)%>% sf::st_sfc(crs = 4326) )
```

Spatial coordinates require a coordinate references system (CRS). For lat-lon coordinates we typically use WGS84 with the EPSG code 4326. `st_polygon()`

creates a simple polygon as a clockwise arrangement of individual coordinates and `st_sfc()`

properly defines this polygon as a geographic area on Earth.

A simple way to interactively inspect this polygon on a world map in R is provided by the `mapview`

package (Appelhans et al. (2023)): `mapview::mapview(research_area_4326)`

.

*Mapview: Interactive Viewing of Spatial Data in r*. https://github.com/r-spatial/mapview.

With the research area properly defined we can move to the next challenge and extract the land area in the research area. For that we have to obtain a dataset with polygons that trace the world’s coastlines. The naturalearthdata project provides open worldwide geodata in different resolutions and in easy to use data formats.

The `rnaturalearth`

package makes it easy to download this data right into `sf`

objects in R.

```
<- rnaturalearth::ne_download(
worldwide_land_outline_4326 scale = 50, type = 'land', category = 'physical',
returnclass = "sf"
)
```

We can then crop the land outline to the research area to obtain the land area we are interested in.

```
<- sf::st_intersection(
research_land_outline_4326
worldwide_land_outline_4326,
research_area_4326 )
```

Using `ggplot2`

, we can finally plot the resulting spatial multi-polygon.

`ggplot() + geom_sf(data = research_land_outline_4326)`

##### 6.3.2.1.2 Projecting the spatial data

At this point we run into a specific issue of mobest: It requires its “independent” spatial and temporal coordinates to be coordinates in a Cartesian system describing Euclidean space.

For the spatial coordinates that means we can not work with latitude and longitude coordinates on a sphere, but have to transform them. We have to apply map projection to represent the curved, two dimensional surface of our planet on a simple plane.

We decided to follow the recommendation of Annoni et al. (2003) and chose ETRS89 Lambert Azimuthal Equal Area coordinate reference system as in EPSG code 3035.

*Map Projections for Europe*, edited by A. Annoni et al., 50–55. European Commission Joint Research Centre. http://mapref.org/LinkedDocuments/MapProjectionsForEurope-EUR-20120.pdf.

Our decision comes at the price of increased inaccuracy especially in the North- and South-East of the research area where we get very far away from the specified centre for EPSG:3035 at 52° latitude and 10° longitude (see Tsoulos (2003) p.53 for a visualization of the deformative effects).

To transform the land outline in the research area from EPSG:4326 to EPSG:3035 we can apply `sf::st_transform()`

.

```
<- research_land_outline_4326 %>%
research_land_outline_3035 ::st_transform(crs = 3035) sf
```

Note how the change in the coordinate system affects the map plot.

`ggplot() + geom_sf(data = research_land_outline_3035)`

##### 6.3.2.1.3 Creating the prediction grid

To finally create the prediction grid we can use `mobest::create_prediction_grid()`

. It takes the land outline polygon and overlays its bounding box with a regular grid (using `sf::st_make_grid()`

), where each cell has the size corresponding to the `spatial_cell_size`

parameter. It then determines the centres of each grid cell and crops the resulting, regular point cloud with the land area.

Note that `spatial_cell_size`

uses the unit of the CRS, so in our case for EPSG:3035 *meters*. That means a value of 50000 translates to one point every 50km.

```
<- mobest::create_prediction_grid(
spatial_pred_grid
research_land_outline_3035,spatial_cell_size = 50000
)
```

The total number of resulting spatial prediction positions is 4738 in this example.

`create_prediction_grid`

returns an object of class `mobest_spatialpositions`

, which is derived from `tibble::tibble()`

. That means we can print it on the R console and it will behave as a `tibble`

. It will also work seamlessly as an input for `ggplot2`

, which we can now use to visualize the point cloud.

```
ggplot() +
geom_sf(data = research_land_outline_3035) +
geom_point(
data = spatial_pred_grid,
mapping = aes(x, y),
color = "red",
size = 0.25
)
```

#### 6.3.2.2 Reading the input samples

mobest requires a set of data points, archaeogenetic samples, to inform the ancestry field interpolation. For each sample the position in space, time and a dependent variable space (e.g. the coordinates in a PCA analysis) must be known. This information must be provided in a specific format. A typical mobest workflow involves preparing a sample list in a .xlsx or .csv table, which could then be read into R and transformed to the correct format.

For this tutorial we rely on the data used and published in Schmid and Schiffels (2023). You can download a simplified version of this dataset here (`samples_basic.csv`

) and load it into R.

`<- readr::read_csv("path/to/your/downloaded/samples_basic.csv") samples_basic `

`samples_basic`

includes the following columns/variables:

Column | Type | Description |
---|---|---|

Sample_ID | chr | A sample identifier |

Latitude | dbl | The latitude coordinate where this sample was recovered |

Longitude | dbl | The longitude coordinate |

Date_BC_AD_Median | int | The median age of this sample in years BC/AD (negative numbers for BC, positive ones for AD) |

MDS_C1 | dbl | The coordinate of this sample on dimension 1 of an MDS analysis. See Schmid and Schiffels (2023) for more details on how this was obtained |

MDS_C2 | dbl | The coordinate of this sample on MDS dimension 2 |

These variables are a minimum for a meaningful mobest run and must be known for all samples. Samples with missing information in any of these columns have to excluded from the input.

Just as for the research area we have to transform the coordinates from longitude and latitude coordinates to a projected system, specifically the same as the one we selected above. To do this we can construct an `sf`

object from the sample table, apply `sf::st_transform()`

and then transform this result back to a `tibble`

with the x and y coordinates of EPSG:3035 in extra columns. This last step makes the code a bit awkward.

```
<- samples_basic %>%
samples_projected # make the tibble an sf object
::st_as_sf(
sfcoords = c("Longitude", "Latitude"),
crs = 4326
%>%
) # transform the coordinates
::st_transform(crs = 3035) %>%
sf# reshape the sf object back into a simple tibble
::mutate(
dplyrx = sf::st_coordinates(.)[,1],
y = sf::st_coordinates(.)[,2]
%>%
) ::st_drop_geometry() sf
```

With the coordinates in the same reference system as the landmass polygons we prepared above we can now combine both in a single figure:

```
ggplot() +
geom_sf(data = research_land_outline_3035) +
geom_point(
data = samples_projected,
mapping = aes(x, y),
color = "darkgreen",
size = 0.25
)
```

A number of samples are outside of the area we want to predict here. That is no problem. They will inform the field in the north-eastern fringes of the area and do no harm. It is much more problematic that some zones within our research area are severely under-sampled. We have to keep sampling gaps like this in mind when we interpret the results of the similarity search.

### 6.3.3 Specifying the search sample

mobest’s similarity search usually takes the perspective of an individual sample for which we want to determine similarity probabilities for a spatial prediction grid at a specific point in time. For this sample, the “search sample”, we require the same information as for the input samples: The position in space, time and the dependent variable space (e.g. PCA or MDS space).

Technically this is only a requirement of the mobest interface. Conceptually such a similarity search only really requires the dependent variable space position of interest. The added benefit of having all information there is the `relative`

time search setting (see below) and a very comprehensive output table for the most common use-case.

In this example we will locate one specific sample with a pretty well studied ancestry history: The sample named `Stuttgart`

published in Lazaridis et al. (2014). We can select it as a subset of our sample table:

*Nature*513 (7518): 409–13. https://doi.org/10.1038/nature13673.

```
<- samples_projected %>%
search_samples ::filter(
dplyr== "Stuttgart_published.DG"
Sample_ID )
```

With this setup the search sample itself will be part of the samples used to inform the ancestry field interpolation (`samples_projected`

). This is no problem - the search sample is a known data point in space and time that can very well be employed to build a better model of the past ancestry distribution. There may be research questions for which this might not be desired, though. Then it can just as well be excluded from the `samples_projected`

table.

### 6.3.4 Running mobest’s interpolation and search function

With the input data, both the spatial prediction grid and the samples to inform the ancestry field interpolation, prepared and ready, we can now run `mobest::locate()`

. For that we first have to split and transform the input into the required data structures.

#### 6.3.4.1 Building the input data for the interpolation

Here is how the interface of `mobest::locate()`

looks:

```
::locate(
mobest# spatiotemporal coordinates of the reference samples
# informing the ancestry field
independent = ...,
# genetic coordinates of the reference samples
dependent = ...,
# ---
# interpolation settings for each ancestry component
kernel = ...,
# ---
# spatiotemporal coordinates of the sample of interest
search_independent = ...,
# genetic coordinates of the sample of interest
search_dependent = ...,
# ---
# spatial search grid: where to search
search_space_grid = ...,
# search time: when to search
search_time = ...,
search_time_mode = ...,
# ---
# should the result be normalized
normalize = ...
)
```

Each of these arguments requires specific input.

##### 6.3.4.1.1 Independent and dependent positions

The `locest()`

arguments `independent`

and `dependent`

take the spatiotemporal and genetic (as for example derived from MDS/PCA) positions of the interpolation-informing samples. The terms *independent* and *dependent* allude to the notion and terminology of a statistical model, where positions in dependent, genetic space are predicted based on positions in independent, spatiotemporal space.

Spatiotemporal positions are encoded in mobest with a custom data type: `mobest_spatiotemporalpositions`

. For the `independent`

argument of `locest()`

we have to construct an object of this type with `mobest::create_spatpos()`

to represent the positions of the input samples in `samples_projected`

.

```
<- mobest::create_spatpos(
ind id = samples_projected$Sample_ID,
x = samples_projected$x,
y = samples_projected$y,
z = samples_projected$Date_BC_AD_Median
)
```

The dependent, genetic variables are also encoded in a custom, tabular type: `mobest_observations`

with the constructor function `mobest::create_observations()`

.

```
<- mobest::create_obs(
dep C1 = samples_projected$MDS_C1,
C2 = samples_projected$MDS_C2
)
```

Note that you can have an arbitrary number of these components with arbitrary names. The only condition is, that the very same set and names are used below for the search samples and for the kernel parameter settings of each dependent variable.

The lengths of the vectors (`samples_projected$...`

) used for `create_spatpos()`

and `create_obs()`

all have to be identical. And their order has to be the same as well: Although the input is distributed over two constructors they describe the same samples.

For the search sample in `search_samples`

, finally, we have to construct objects of the same type and structure:

```
<- mobest::create_spatpos(
search_ind id = search_samples$Sample_ID,
x = search_samples$x,
y = search_samples$y,
z = search_samples$Date_BC_AD_Median
)<- mobest::create_obs(
search_dep C1 = search_samples$MDS_C1,
C2 = search_samples$MDS_C2
)
```

##### 6.3.4.1.2 Kernel parameter settings

The `locest()`

argument `kernel`

takes an object of the class `mobest_kernelsetting`

. This type encodes kernel configurations for each dependent variable, so the parameters for the Gaussian process regression (GPR) interpolation that should be used for this variable. These include mostly the lengthscale parameters in space (x and y) and time, as well as the nugget parameter (Gramacy (2020), specifically here). In very simple terms:

*Surrogates: Gaussian Process Modeling, Design and Optimization for the Applied Sciences*. Boca Raton, Florida: Chapman Hall/CRC.

*Lengthscale parameters*: How far in space and time should an individual sample’s genetic position inform the interpolated field.*Nugget*: Error term to model local variability of the dependent variable, so for observations from the same position in space and time.

Here is a possible kernel configuration for our example. We construct two kernel settings, one for each ancestry component, with `mobest::create_kernel()`

in `mobest::create_kernset()`

.

```
kernset <- mobest::create_kernset(
C1 = mobest::create_kernel(
dsx = 800 * 1000, dsy = 800 * 1000, dt = 800,
g = 0.1
),
C2 = mobest::create_kernel(
dsx = 800 * 1000, dsy = 800 * 1000, dt = 800,
g = 0.1
)
)
```

Note how we scale the lengthscale parameters: `dsx`

and `dsy`

are set in meters (800 * 1000m = 800km) and `dt`

in years (800y). `g`

is dimensionless. With the setting specified here both dependent variables will be interpolated with the same, very smooth (several hundred kilometres and years in diameter) kernel.

The main question naturally arising from this, is how to set these parameters for a given dataset and research question. There are various empirical ways to find optimal values through numerical optimization. See Supplementary Text 2 of Schmid and Schiffels (2023) and the mobest documentation for the approaches we applied.

##### 6.3.4.1.3 Search positions

With input data and settings out of the way we can now specify the points in space and time where we actually want to perform the search. For these positions the GPR model is queried to return a mean and error, which are in turn used to calculate the probability density of a specific dependent variable space position, e.g. a specific coordinate on the first coordinate of an MDS analysis.

We already performed all necessary work for the `search_space_grid`

argument, so the spatial positions of the prediction grid. We can just enter `spatial_pred_grid`

here.

The search time can be specified as an integer vector of years: e.g. `search_time = c(-500, -200, 100)`

. This vector gets interpreted by `mobest::locate()`

in two different ways, which can be selected with the switch argument `search_time_mode`

. `search_time_mode`

can either be `"relative"`

(which is the default!) or `absolute`

.

`"relative"`

: The`search_time`

is interpreted as a \(\Delta t\) relative to the age of the search sample(s). Negative values point to ages that are older then the sample age, so in their relative past, and positive ones to younger ages in their relative future. In this example`-500`

would be interpreted as 500 years prior to the year the Stuttgart sample presumably died (so -5242-500 = -5742 BC/AD), and 100 as an age 100 years after their death (so -5242+100 = -5142 BC/AD).`"absolute"`

: The values in`search_time`

are simply interpreted as absolute ages in years BC/AD.

For this example we will set the search time to an `"absolute"`

value.

```
= -6800
search_time = "absolute" search_time_mode
```

This will search at exactly one point in time; a single timeslice 6800 BC.

##### 6.3.4.1.4 Normalization

The last relevant option of `locate()`

, `normalize`

, concerns the normalization of the output. mobest’s search calculates likelihoods for each search point. This is a dimensionless measure that is hard to compare across multiple runs with different parameter settings. If `normalize`

is set to `TRUE`

, then the densities for sets of spatial points that share all other parameters (including the timeslice) are rescaled to a sum of one, so to proper probabilities.

We assume users generally want to use mobest, specifically `locate()`

, to calculate similarity probability density maps for individual samples, time slices and parameter settings. The most natural normalization for this case is to unify the scaling of these maps. This renders them comparable. `normalize`

should therefore be set to `TRUE`

for basic applications. This is also encoded as the the default setting.

#### 6.3.4.2 Calling `mobest::locate()`

In the previous sections we have thoroughly prepared the input for a first, simple run of `mobest::locate()`

. We can now finally call the function.

```
<- mobest::locate(
search_result independent = ind,
dependent = dep,
kernel = kernset,
search_independent = search_ind,
search_dependent = search_dep,
search_space_grid = spatial_pred_grid,
search_time = -6800,
search_time_mode = "absolute"
)
```

This typically runs for a couple of seconds, uses every available processor core and returns an object `search_result`

, which we will inspect below.

### 6.3.5 Inspecting the computed results

`mobest::locate()`

returns an object of class `mobest_locateoverview`

. It includes the relevant information for visualization and further processing of the analysis results.

#### 6.3.5.1 The mobest_locateoverview table

`mobest_locateoverview`

is derived from `tibble`

and has a large set of columns, many not immediately relevant to the basic example here. This applies especially for the variables documenting the excessive permutation mechanics hidden behind the relatively simple interface of `mobest::locate()`

. `locate()`

is, in fact, a wrapper function for the more flexible function `mobest::locate_multi()`

, which can handle permutations in various additional input parameters.

Each row of the `mobest_locateoverview`

table stores the calculated interpolated mean, error and similarity probability (`field_mean`

, `field_sd`

, `probability`

) for one permutation of the input point positions in independent and dependent variable space (`independent_table_id`

and `dependent_setting_id`

), one dependent variable `dependent_var_id`

, one iteration of the kernel settings (`kernel_setting_id`

: `dsx`

, `dsy`

, `dt`

, `g`

), one prediction grid point emerging as a combination of spatial grid and search timeslice (`pred_grid_id`

: `field_id`

, `field_geo_id`

, `field_x`

, `field_y`

, `field_z`

, `search_time`

) and finally one search sample (`search_id`

, `search_x`

, `search_y`

, `search_z`

, `search_measured`

).

Here is a list of the variables returned in `mobest_locateoverview`

for each of these result iterations.

Column | Description |
---|---|

independent_table_id | Identifier of the spatiotemporal position permutation |

dependent_setting_id | Identifier of the dependent variable space position permutation |

dependent_var_id | Identifier of the dependent variable |

kernel_setting_id | Identifier of the kernel setting permutation |

pred_grid_id | Identifier of the spatiotemporal prediction grid |

dsx | Kernel lengthscale parameter on the spatial x axis |

dsy | Kernel lengthscale parameter on the spatial y axis |

dt | Kernel lengthscale parameter on the temporal axis |

g | Kernel nugget parameter |

field_id | Identifier of the spatiotemporal prediction point |

field_x | Spatial x axis coordinate of the prediction point |

field_y | Spatial y axis coordinate of the prediction point |

field_z | Temporal coordinate (age) of the prediction point |

field_geo_id | Identifier of the spatial prediction point |

field_mean | Mean value predicted by the GPR model for the dependent variable |

field_sd | Error term predicted by the GPR model for the dependent variable |

search_id | Identifier of the search sample |

search_x | Spatial x axis coordinate of the search sample |

search_y | Spatial y axis coordinate of the search sample |

search_z | Temporal coordinate (age) of the search sample |

search_time | Search time as provided by the user in `locate()` ’s `search_time` argument |

search_measured | Genetic coordinate of the search sample in the dependent variable space |

probability | Probability density for `search_measured` given all other parameters |

As a result of the permutation of parameters, the size of the prediction grid and the number of search points, the number of rows in a `mobest_locateoverview`

table can be calculated as a product of the individual counts of all relevant entities. One way to quickly validate the output of `locate()`

and `locate_multi()`

is to calculate the number of expected results based on the input and compare it with the actual number of rows in the output. For our example this calculation is fairly simple:

We have:

- \(1\) set of input point positions in independent variable space (
`independent_table_id`

) - \(1\) set of input point positions in dependent variable space (
`dependent_setting_id`

) - \(2\) dependent variables (
`dependent_var_id`

) - \(1\) set of kernel parameter settings (
`kernel_setting_id`

) - \(4738\) spatial prediction grid positions
- \(1\) time slice of interest
- \(1\) search sample

This means we expect exactly \(2 * 4738 = 9476\) rows in `search_result`

, which we can confirm with `nrow(search_result)`

.

#### 6.3.5.2 Creating similarity probability maps for individual dependent variables

The most basic similarity probability map we can create with `search_result`

is a map for just one parameter permutation, including only one dependent variable. In this case the relevant similarity probability observations are easy to obtain. We can just filter by `dependent_var_id`

to only include either `C1`

or `C2`

.

```
<- search_result %>%
result_C1 ::filter(dependent_var_id == "C1") dplyr
```

And this is then easy to plot with `geom_raster()`

. We can then plot C1 and C2 together using `cowplot::plot_grid()`

.

```
<- ggplot() +
p_C1 geom_raster(
data = result_C1,
mapping = aes(x = field_x, y = field_y, fill = probability)
+
) coord_fixed()
# for C2
<- search_result %>%
result_C2 ::filter(dependent_var_id == "C2")
dplyr<- ggplot() +
p_C2 geom_raster(
data = result_C2,
mapping = aes(x = field_x, y = field_y, fill = probability)
+
) coord_fixed()
# arrange both plots together
::plot_grid(p_C1, p_C2, labels = c("C1", "C2")) cowplot
```

#### 6.3.5.3 Combining the information from multiple dependent variables

The results for individual dependent variables, so ancestry components like MDS or PCA dimensions, can be informative, but are usually under-powered to exclude highly improbable search results. Generally combining multiple ancestry components improves the accuracy of the results for individual samples, and we think this is best done by multiplying the results for the different dependent variables. This way spatial areas with high similarity probability for all dependent variables are naturally up-weighted, whereas areas that are unlikely similar for some dependent variables are down-weighted.

To perform the multiplication (and the re-normalization afterwards), mobest includes a function `mobest::multiply_dependent_probabilities()`

. It works on objects of type `mobest_locateoverview`

and yields tabular objects of type `mobest_locateproduct`

. `multiply_dependent_probabilities()`

is aware of the parameter permutations potentially encoded in the `mobest_locateoverview`

table. It only combines the probabilities for dependent variables that share all other parameters. The number of rows in `mobest_locateproduct`

will therefore be \(\frac{\text{Number of rows in mobest\_locateoverview}}{\text{Number of dependent variables}}\).

If we call it for `search_result`

the output will thus have \(9476/2=4738\) rows.

```
<-
search_product ::multiply_dependent_probabilities(search_result) mobest
```

`mobest_locateproduct`

tables feature a perfect subset of the columns in `mobest_locateoverview`

. We can plot the combined similarity probability map with the code already applied for the individual dependent variables.

```
ggplot() +
geom_raster(
data = search_product,
mapping = aes(x = field_x, y = field_y, fill = probability)
+
) coord_fixed()
```

This concludes a very basic similarity search workflow. Please see the **documentation at https://nevrome.de/mobest** for various other tutorials describing more advanced applications of mobest, starting with better map plotting.