I wanted to know if there was a one-to-one mapping for a separate project. Working on your example helped me find a little mistake, so thanks for asking.
The basic idea is to create a table(x,y) and check to see if it could be diagonal. Obviously, if the variables are laid out "just so", then the table will be diagonal and easy to discern, but in your case, the alignment is a little jagged:
table(DF$A, DF$B)
H M W
0 3 0 0
1 0 0 5
2 0 2 0
The strategy to correct that for me was along these lines
> dfu <- unique(DF)
> table(dfu[ , 1], dfu[ ,2])
H M W
0 1 0 0
1 0 0 1
2 0 1 0
Then calculate the sums of the rows and columns.
If either the row sum or column sum is greater than one, then this is not a one-to-one relationship. If none of the row sums or column sums exceed 1, then the mapping is one-to-one. My original mistake was not checking uniquness of mapping in both directions. I'm aware I could calculate eigenvalues to find out same thing, but that is not so obvious and much slower in preliminary testing.
Here's my function. Pass in either 2 columns or one matrix with 2 columns, this returns TRUE or FALSE
onetoone <- function(w, z){
if (is.matrix(w)){
if(dim(w)[2] != 2) stop("need 2 column matrix")
dfu <- unique(w)
} else {
dfu <- unique(cbind(w,z))
}
return( !any(rowSums(table(dfu[ , 1], dfu[ , 2])) > 1) &&
!any(colSums(table(dfu[ , 1], dfu[ , 2])) > 1) )
}
With your data:
> DF <- data.frame(A = c(0,2,0,1,2,1,0,1,1,1),
+ B = c('H','M','H','W','M','W','H','W','W','W'));
> onetoone(DF$A, DF$B)
[1] TRUE
> onetoone(as.matrix(DF))
[1] TRUE
> DF$C <- rnorm(10)
> onetoone(DF$A, DF$C)
[1] FALSE
That works fine on small problems with just a few thousand rows.
In my actual application, the columns are 10000s of rows long, and I ended up breaking them into sections and as soon as one section failed the test, then I returned a false.