Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected behavior of modulo (%%) operations on negative cell values #1661

Closed
klaus-huebert opened this issue Dec 4, 2024 · 2 comments
Closed

Comments

@klaus-huebert
Copy link

Many thanks for the amazing terra package! I hope this bug report is helpful.

I searched open issues on GitHub and checked stack overflow [terra] for previous discussions of this topic and did not find any.

There seems to be a problem with the terra implementation of %% operating on negative cell values. A simple example is that -5%%2 is 1 in base R arithmetic, but a SpatRaster cell of -5 %% 2 yields a SpatRaster cell of -1. Looking more closely, it seems that for negative numbers the terra implementation of %% uses the next/larger integer multiple as a reference point, instead of the previous/smaller one (see example 2 below). I replicated this behavior across various platforms and operating systems, and included the session info for one of them.

Cheers,
-Klaus Huebert

library(terra)
# terra 1.7.83
# Warning message:
# package ‘terra’ was built under R version 4.4.2

### example 1

-5%%2 # as expected
# [1] 1

matrix(-5,1,1)%%2 # as expected
#      [,1]
# [1,]    1

rast(matrix(-5,1,1))%%2 # not as expected
# class       : SpatRaster 
# dimensions  : 1, 1, 1  (nrow, ncol, nlyr)
# resolution  : 1, 1  (x, y)
# extent      : 0, 1, 0, 1  (xmin, xmax, ymin, ymax)
# coord. ref. :  
# source(s)   : memory
# name        : lyr.1 
# min value   :    -1 
# max value   :    -1 

### example 2

c(-5:5)%%3
# [1] 1 2 0 1 2 0 1 2 0 1 2

matrix(c(-5:5),11,1)%%3
#       [,1]
#  [1,]    1
#  [2,]    2
#  [3,]    0
#  [4,]    1
#  [5,]    2
#  [6,]    0
#  [7,]    1
#  [8,]    2
#  [9,]    0
# [10,]    1
# [11,]    2

values(rast(matrix(c(-5:5),1,11))%%3)
#       lyr.1
#  [1,]    -2
#  [2,]    -1
#  [3,]     0
#  [4,]    -2
#  [5,]    -1
#  [6,]     0
#  [7,]     1
#  [8,]     2
#  [9,]     0
# [10,]     1
# [11,]     2

sessionInfo()
# R version 4.4.1 (2024-06-14 ucrt)
# Platform: x86_64-w64-mingw32/x64
# Running under: Windows 11 x64 (build 22631)
# 
# Matrix products: default
# 
# 
# locale:
# [1] LC_COLLATE=English_United States.utf8  LC_CTYPE=English_United States.utf8    LC_MONETARY=English_United States.utf8 LC_NUMERIC=C                          
# [5] LC_TIME=English_United States.utf8    
# 
# time zone: America/New_York
# tzcode source: internal
# 
# attached base packages:
# [1] stats     graphics  grDevices utils     datasets  methods   base     
# 
# other attached packages:
# [1] terra_1.7-83
# 
# loaded via a namespace (and not attached):
# [1] compiler_4.4.1   tools_4.4.1      Rcpp_1.0.12      codetools_0.2-20

terra::gdal(lib="all")
#     gdal     proj     geos 
#  "3.9.3"  "9.4.1" "3.12.2" 
@rhijmans
Copy link
Member

rhijmans commented Dec 4, 2024

Thank you for pointing out this inconsistency. This is a bit tricky (see wikipedia and ?Arithmetic; and returning a negative modulo seems more informative; and you could do abs(x %% 2). But consistency with the R base language is probably more important, so I have changed to code to achieve that:

library(terra)
v <- -4:4
x <- rast(nrow=3, ncol=3, vals=v)
y <- rast(nrow=3, ncol=3, vals=2)
 
v %% 2
#[1] 0 1 0 1 0 1 0 1 0
values(x %% y)[,1]
#[1] 0 1 0 1 0 1 0 1 0
values(x %% 2)[,1]
#[1] 0 1 0 1 0 1 0 1 0

@klaus-huebert
Copy link
Author

A bit tricky indeed - thanks for the quick response!

netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this issue Jan 5, 2025
# version 1.8-5

## bug fixes

- `Spatsample(method='stratified', ext=e)` returned the wrong sampling
  coordinates [#1628](rspatial/terra#1628)
  by Barnabas Harris

- `spatSample(method='stratified')` could fail with small sample sizes
  [#1503](rspatial/terra#1503) by karluf

- transparency (alpha) did not work with RGB
  plotting. [#1642](rspatial/terra#1642) by
  Timothée Giraud

- rasterization failed on very large rasters
  [#1636](rspatial/terra#1636) by Mary
  Fisher, [#1463](rspatial/terra#1463) by
  Nic Spono and [#1281](rspatial/terra#1281)
  by Sebastian Dunnett

- `tmpFiles` only looked in the default temp files folder
  [#1630](rspatial/terra#1630) by
  smckenzie1986

- `where.min` did not work well if there were negative values
  [#1634](rspatial/terra#1634) by Michael
  Sumner

- `plet<SpatRaster>` now works for RGB rasters and rasters with a
  color table [#1596](rspatial/terra#1596)
  by Agustin Lobo

- `vect<MULTIPOINT WKT>` did not work properly
  [#1376](rspatial/terra#1376) by
  silasprincipe

- `compareGeom<SpatVector>` did not work
  [#1654](rspatial/terra#1654) by Jason
  Flower

- `buffer<SpatVector>` is now more accurate buffers for lonlat
  polygons [#1616](rspatial/terra#1616) by
  Roberto Amaral-Santos

- `terra:interpNear` used square windows, not circles, beyond 100
  points [#1509](rspatial/terra#1509) by
  Jean-Luc Dupouey

- `vect` read INT64 fields as integers, sometimes leading to
  overflows. [#1666](rspatial/terra#1666) by
  bengannon-fc

- `plot` showed a legend title even if none was requestd if title
  parameters were specified
  . [#1664](rspatial/terra#1664) by Márcia
  Barbosa



## enhancements

n- improved documentation of `writeVector` overwrite when using
  layers. [#1573](rspatial/terra#1573) by
  Todd West

- improved treatment of (supposedly) flipped rasters by Timothée
  Giraud [#1627](rspatial/terra#1627) and
  fchianucci [#1646](rspatial/terra#1646)

- added `map.pal("random")`
  [#1631](rspatial/terra#1631) by Agustin
  Lobo

- expressions can now be used in legend titles
  [#1626](rspatial/terra#1626) by Noah
  Goodkind

- `app` and `tapp` now emit a warning when factors are coerced to
  numeric [#1566](rspatial/terra#1566) by
  shuysman

- `plet<SpatRaster>` now has argument "stretch" for RGB rasters
  [#1596](rspatial/terra#1596) by Agustin

- `%%` and `%/%` now behave the same for SpatRaster as for (base R)
  numbers [#1661](rspatial/terra#1661) by
  Klaus Huebert

## new

- `patches` with option `valus=TRUE` can now distinguish regions based
  on their cell values (instead of only NA vs not-NA)
  [#495](rspatial/terra#495) by Jakub
  Nowosad and [#1632](rspatial/terra#1632)
  by Agustin Lobo

- `rowSums`, `rowMeans`, `colSums` and `colMeans` for SpatRaster

- `metags` for SpatRasterDataset
  [#1624](rspatial/terra#1624) by Andrea
  Manica

- `metags` for layers (bands) of SpatRaster are now saved to and read
  from GTiff files
  [#1071](rspatial/terra#1071) by Mike
  Koontz

- `global` has new effcient functions "anyNA" and "anynotNA"
  [#1540](rspatial/terra#1540) by Kevin J
  Wolz

- `wrap`, `saveRDS` and `serialize` for
  SpatExtent. [#1430](rspatial/terra#1430)
  by BastienFR

- `vect<SpatGraticule>` method suggested in relation to [tidyterra
  #155](dieghernan/tidyterra#155) by Diego
  Hernangómez

- `toMemory<SpatRaster>` and `<SpatRasterDataset>` methods
  [#1660](rspatial/terra#1660) by Derek Friend
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants