Manually specifying common grid to allow overlap comparison

32 views
Skip to first unread message

Sam Langlois

unread,
Nov 18, 2025, 7:26:55 AMNov 18
to ctmm R user group
Hi,

I'm working with a seabird GPS dataset which has been classified into three behaviours, as well as into day and night. I'm trying to carry out UD overlap comparisons at the pop level (following pkde) between different behaviours, as well as between day and night. 

Due to having multiple years of data, I currently use a for loop to subset the specific year-behaviour-day/night combination I want to calculate UDs for. However, I can't use the overlap function due to inconsistent grids. Can I manually specify a grid resolution within the akde function so all outputs are on this grid before running pkde()? Creating a large list of of all the telemetry objects I am interested in so they can be passed through akde together to achieve the same grid res would be too memory intensive.

Many thanks!

Sam Langlois

unread,
Nov 19, 2025, 12:26:34 PMNov 19
to ctmm R user group
Hi Chris,

Just to follow up on this, this is the approach I am taking to specify a common grid, although I still get the same error message about grids being inconsistent. I tried including/excluding the grid argument from the pkde function but nothing changes. Any thoughts?

I first estimated UDs for a single individual to use as a reference grid.

DATA <- as.telemetry(
  data,
  timeformat = "auto",
  timezone = "UTC",
  projection=  "+proj=merc +lon_0=0 +k=1 +datum=WGS84 +units=m +no_defs"  ,
  dt.hot = NA,
  timeout = Inf,
  na.rm = "row",
  mark.rm = FALSE,
  keep = FALSE,
  drop = TRUE
)
grid_ref_tele <- DATA[[5]]

guess <- ctmm.guess(grid_ref_tele, interactive = FALSE)
FITS <- ctmm.select(grid_ref_tele, guess)

dt <- 5 %#% 'min' 
GRID_REF_UD <- akde(grid_ref_tele,FITS,dt=dt, weights=FALSE,trace=TRUE)  # make individual UD to use as grid reference

I then run the following loop which produces UDs from several individuals for several years and stores the PKDEs in a list (I've removed some of the code for clarity)

for(species_s in species_list){
 
  # Filter by species
  species_filter <- all_states_filtered %>% filter(species == species_s)
 
  for(year_y in unique(species_filter$Year)){
   
    # Filter by year
    year_filter <- species_filter %>% filter(Year == year_y)
   
    # Convert to telemetry objects (one per individual)
    DATA <- as.telemetry(
      year_filter,
      timeformat = "auto",
      projection=  "+proj=merc +lon_0=0 +k=1 +datum=WGS84 +units=m +no_defs"  ,
      timezone = "UTC",
      dt.hot = NA,
      timeout = Inf,
      na.rm = "row",
      mark.rm = FALSE,
      keep = FALSE,
      drop = TRUE
    )
   
    # ===========================
    # FIT INDIVIDUAL MODELS USING lapply
    # ===========================
    FITS <- lapply(DATA, function(x){
      guess <- ctmm.guess(x, interactive = FALSE)
      ctmm.select(x, guess)
    })
   
    dt <- 5 %#% 'min' # from dt.plot
   
    UDS <- akde(DATA,FITS,weights=FALSE, dt = dt,trace=TRUE, grid=GRID_REF_UD) # make individual IDs, takes c. 30 mins/year/species
   
     PKDE <- pkde(DATA, UDS, kernel="individual", grid=GRID_REF_UD, weights = FALSE) # make population UDs
   
    all_PKDE_list <- c(all_PKDE_list, list(PKDE)) #save PKDEs in list
    
  }
}

all_PKDEs_overlaps_95 <- overlap(all_PKDE_list, method="Bhattacharyya", level=0.95, debias=T)
Error in grid.intersection(UD) : Inconsistent grid resolutions.

Thanks!
Sam

Allie Anderson

unread,
Nov 19, 2025, 3:24:44 PMNov 19
to Sam Langlois, ctmm R user group
Hi Sam,

Just responding in case this option is helpful to you. Chris might have a better option. Here's what I have been doing:

## 6oo) CREATE CONSISTENT GRID FOR ALL INDIVIDUALS ####
grid_extent <- ctmm::extent(tlm_lst)  

# GET X AND Y LIMS FOR BOUNDING BOX:
x_range <- diff(grid_extent$x)
y_range <- diff(grid_extent$y)

# Add buffer factor:
buffer_factor <- 0.30 # increased by 30% to add more space if akdes and CIs are larger. May not need this much

# Multiply by buffer factor
buffered_extent <- list(
  x = c(grid_extent$x[1] - x_range * buffer_factor,
      grid_extent$x[2] + x_range * buffer_factor),
  y = c(grid_extent$y[1] - y_range * buffer_factor,
      grid_extent$y[2] + y_range * buffer_factor))

# DEFINE GRID RESOLUTION:
res <- 100  # in meters
xseq <- seq(buffered_extent$x[1], buffered_extent$x[2], by = res)
yseq <- seq(buffered_extent$y[1], buffered_extent$y[2], by = res)
shared_grid <- list(x = xseq, y = yseq)

## 6a) CREATE AKDES BASED ON TOP MODEL FOR ALL ####
akde_lst <- purrr::map2(tlm_lst, ctmm_fit_lst,
            ~akde(.x, .y[[1]], #top model
                 grid = shared_grid)) 

--
You received this message because you are subscribed to the Google Groups "ctmm R user group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ctmm-user+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/ctmm-user/4789f145-b7ea-429f-a6c6-f5e5d4e83ec6n%40googlegroups.com.

Christen Fleming

unread,
Dec 2, 2025, 2:47:14 PMDec 2
to ctmm R user group
Hi Sam,

The problem here is that you are importing the data multiple times, which is giving the data different automated projections.
You could either filter after importing or you could reproject all datasets after importing, but before fitting the models, with
PROJ <- projection(DATA)
to extract the projection from a dataset and
projection(DATA) <- PROJ
to apply the projection to a dataset.
I would not use the Mercator projection for analysis. You can import the entire dataset and the automated projection will be centered on the data.

Best,
Chris
Reply all
Reply to author
Forward
0 new messages