Shrink data.frame from 8x8 to 4x4 by taking 2x2 spatial averages

Viewed 104

Let's assume I have got the following dataframe where each observation represents a given variable in a specific point in a 2D space:

data = data.frame(col1 = c(1,2,3,4,5,6,7,8), col2 = c(2,3,'NA',5,6,7,8,9), col3 = c(3,4,5,6,7,8,9,10), col4 = c(2,3,4,1,2,6,7,8),
                  col5 = c(2,3,'NA','NA',6,7,8,9), col6 = c(1,2,3,5,6,7,8,9), col7 = c(1,2,3,4,6,7,'NA','NA'), col8 = c(1,2,3,4,5,6,'NA','NA'))

> print(data)
  col1 col2 col3 col4 col5 col6 col7 col8
1    1    2    3    2    2    1    1    1
2    2    3    4    3    3    2    2    2
3    3   NA    5    4   NA    3    3    3
4    4    5    6    1   NA    5    4    4
5    5    6    7    2    6    6    6    5
6    6    7    8    6    7    7    7    6
7    7    8    9    7    8    8   NA   NA
8    8    9   10    8    9    9   NA   NA

The dataframe is 8x8 (8 cols and 8 rows) and therefore 36 obs in total.

I need to shrink it to a 4x4 dataframe by making the average of each 2x2 observations group.

The 2x2 NAs group should return a NA value, whereas if within a 2x2 group there exists < 4 NAs the average has to exclude them, e.g. mean of 2,2,2,NA = 2.

Here my desired output:

newcol1  newcol2  newcol3  newcol4
   2        3        2       1.50
   4        4        4       3.50
   6       5.75     6.50      6
   8       8.50     8.50      NA

I think that I could solve this with a for loop and here is what I tried with no success:

a = 1
b = 2

for (i in 1:15) {
      test[[i]] = mean(c(data[a,a], data[a,b], data[b,a], data[b,b]))
      test[[i]] = mean(c(data[a+i,a+i], data[a+i,b+i], data[b+i,a+i], data[b+i,b+i]))
}

I searched a lot online but I couldn't find any similar question or solution.

Any suggestion?

Is there any R package that can do this kind of spatial analysis?

2 Answers

From linked post, using @flodel answer, replacing sum with mean:

a <- as.matrix(data)

#convert matrix from char to num, ignore warnings, they are due to NAs
class(a) <- "numeric" 

#adapted from @flodel https://stackoverflow.com/a/16884987/680068
res <- tapply(a, list((row(a) + 1L) %/% 2L, (col(a) + 1L) %/% 2L), mean, na.rm = TRUE)

# remove NANs
res[ is.nan(res) ] <- NA
res
#   1    2   3   4
# 1 2 3.00 2.0 1.5
# 2 4 4.00 4.0 3.5
# 3 6 5.75 6.5 6.0
# 4 8 8.50 8.5  NA

This is a dumb solution (not terribly generalizable), but will work for the limited example. I imagine there's a cleverer way.

R8toR4 <- function(mat) {
  stopifnot(identical(dim(mat), c(8L, 8L)))

  out <- matrix(nrow = 4, ncol = 4)

  for (i in 1:nrow(out)) {
    for (j in 1:ncol(out)) {
      submatrix <- mat[i * 2 - c(1, 0), j * 2 - c(1, 0)]
      if (all(is.na(submatrix))) {
        out[i, j] <- NA
      } else {
        out[i, j] <- mean(submatrix, na.rm = TRUE)
      }
    }
  }

  out
}


DataMatrix <- as.matrix(data)
R8toR4(DataMatrix)
     [,1] [,2] [,3] [,4]
[1,]    2 3.00  2.0  1.5
[2,]    4 4.00  4.0  3.5
[3,]    6 5.75  6.5  6.0
[4,]    8 8.50  8.5   NA
Related