Permutations of 3 elements within 6 positions - arrays

I'm looking to permute (or combine) c("a","b","c") within six positions under the condition to have always sequences with alternate elements, e.g abcbab.
Permutations could easily get with:
abc<-c("a","b","c")
permutations(n=3,r=6,v=abc,repeats.allowed=T)
I think is not possible to do that with gtools, and I've been trying to design a function for that -even though I think it may already exist.

Since you're looking for permutations, expand.grid can work as well as permutations. But since you don't want like-neighbors, we can shorten the dimensionality of it considerably. I think this is legitimate random-wise!
Up front:
r <- replicate(6, seq_len(length(abc)-1), simplify=FALSE)
r[[1]] <- c(r[[1]], length(abc))
m <- t(apply(do.call(expand.grid, r), 1, cumsum) %% length(abc) + 1)
m[] <- abc[m]
dim(m)
# [1] 96 6
head(as.data.frame(cbind(m, apply(m, 1, paste, collapse = ""))))
# Var1 Var2 Var3 Var4 Var5 Var6 V7
# 1 b c a b c a bcabca
# 2 c a b c a b cabcab
# 3 a b c a b c abcabc
# 4 b a b c a b babcab
# 5 c b c a b c cbcabc
# 6 a c a b c a acabca
Walk-through:
since you want all recycled permutations of it, we can use gtools::permutations, or we can use expand.grid ... I'll use the latter, I don't know if it's much faster, but it does a short-cut I need (more later)
when dealing with constraints like this, I like to expand on the indices of the vector of values
however, since we don't want neighbors to be the same, I thought that instead of each row of values being the straight index, we cumsum them; by using this, we can control the ability of the cumulative sum to re-reach the same value ... by removing 0 and length(abc) from the list of possible values, we remove the possibility of (a) never staying the same, and (b) never increasing actually one vector-length (repeating the same value); as a walk-through:
head(expand.grid(1:3, 1:2, 1:2, 1:2, 1:2, 1:2), n = 6)
# Var1 Var2 Var3 Var4 Var5 Var6
# 1 1 1 1 1 1 1
# 2 2 1 1 1 1 1
# 3 3 1 1 1 1 1
# 4 1 2 1 1 1 1
# 5 2 2 1 1 1 1
# 6 3 2 1 1 1 1
Since the first value can be all three values, it's 1:3, but each additional is intended to be 1 or 2 away from it.
head(t(apply(expand.grid(1:3, 1:2, 1:2, 1:2, 1:2, 1:2), 1, cumsum)), n = 6)
# Var1 Var2 Var3 Var4 Var5 Var6
# [1,] 1 2 3 4 5 6
# [2,] 2 3 4 5 6 7
# [3,] 3 4 5 6 7 8
# [4,] 1 3 4 5 6 7
# [5,] 2 4 5 6 7 8
# [6,] 3 5 6 7 8 9
okay, that doesn't seem that useful (since it goes beyond the length of the vector), so we can invoke the modulus operator and a shift (since modulus returns 0-based, we want 1-based):
head(t(apply(expand.grid(1:3, 1:2, 1:2, 1:2, 1:2, 1:2), 1, cumsum) %% 3 + 1), n = 6)
# Var1 Var2 Var3 Var4 Var5 Var6
# [1,] 2 3 1 2 3 1
# [2,] 3 1 2 3 1 2
# [3,] 1 2 3 1 2 3
# [4,] 2 1 2 3 1 2
# [5,] 3 2 3 1 2 3
# [6,] 1 3 1 2 3 1
To verify this works, we can do a diff across each row and look for 0:
m <- t(apply(expand.grid(1:3, 1:2, 1:2, 1:2, 1:2, 1:2), 1, cumsum) %% 3 + 1)
any(apply(m, 1, diff) == 0)
# [1] FALSE
to automate this to an arbitrary vector, we enlist the help of replicate to generate the list of possible vectors:
r <- replicate(6, seq_len(length(abc)-1), simplify=FALSE)
r[[1]] <- c(r[[1]], length(abc))
str(r)
# List of 6
# $ : int [1:3] 1 2 3
# $ : int [1:2] 1 2
# $ : int [1:2] 1 2
# $ : int [1:2] 1 2
# $ : int [1:2] 1 2
# $ : int [1:2] 1 2
and then do.call to expand it.
one you have the matrix of indices,
head(m)
# Var1 Var2 Var3 Var4 Var5 Var6
# [1,] 2 3 1 2 3 1
# [2,] 3 1 2 3 1 2
# [3,] 1 2 3 1 2 3
# [4,] 2 1 2 3 1 2
# [5,] 3 2 3 1 2 3
# [6,] 1 3 1 2 3 1
and then replace each index with the vector's value:
m[] <- abc[m]
head(m)
# Var1 Var2 Var3 Var4 Var5 Var6
# [1,] "b" "c" "a" "b" "c" "a"
# [2,] "c" "a" "b" "c" "a" "b"
# [3,] "a" "b" "c" "a" "b" "c"
# [4,] "b" "a" "b" "c" "a" "b"
# [5,] "c" "b" "c" "a" "b" "c"
# [6,] "a" "c" "a" "b" "c" "a"
and then we cbind the united string (via apply and paste)
Performance:
library(microbenchmark)
library(dplyr)
library(tidyr)
library(stringr)
microbenchmark(
tidy1 = {
gtools::permutations(n = 3, r = 6, v = abc, repeats.allowed = TRUE) %>%
data.frame() %>%
unite(united, sep = "", remove = FALSE) %>%
filter(!str_detect(united, "([a-c])\\1"))
},
tidy2 = {
filter(unite(data.frame(gtools::permutations(n = 3, r = 6, v = abc, repeats.allowed = TRUE)),
united, sep = "", remove = FALSE),
!str_detect(united, "([a-c])\\1"))
},
base = {
r <- replicate(6, seq_len(length(abc)-1), simplify=FALSE)
r[[1]] <- c(r[[1]], length(abc))
m <- t(apply(do.call(expand.grid, r), 1, cumsum) %% length(abc) + 1)
m[] <- abc[m]
},
times=10000
)
# Unit: microseconds
# expr min lq mean median uq max neval
# tidy1 1875.400 2028.8510 2446.751 2165.651 2456.051 12790.901 10000
# tidy2 1745.402 1875.5015 2284.700 2000.051 2278.101 50163.901 10000
# base 796.701 871.4015 1020.993 919.801 1021.801 7373.901 10000
I tried the infix (non-%>%) tidy2 version just for kicks, and though I was confident it would theoretically be faster, I didn't realize it would shave over 7% off the run-times. (The 50163 is likely R garbage-collecting, not "real".) The price we pay for readability/maintainability.

There are probably cleaner methods, but here ya go:
abc <- letters[1:3]
library(tidyverse)
res <- gtools::permutations(n = 3, r = 6, v = abc, repeats.allowed = TRUE) %>%
data.frame() %>%
unite(united, sep = "", remove = FALSE) %>%
filter(!str_detect(united, "([a-c])\\1"))
head(res)
united X1 X2 X3 X4 X5 X6
1 ababab a b a b a b
2 ababac a b a b a c
3 ababca a b a b c a
4 ababcb a b a b c b
5 abacab a b a c a b
6 abacac a b a c a c
If you want a vector, you can use res$united or add %>% pull(united) as an additional step at the end of the pipes above.

Related

Permutations of 3 elements within 6 positions: one equal neighbour

Taking into account the answers in this post Permutations of 3 elements within 6 positions, I think it's worth to open a new discussion about how ordering the elements.
The first condition was to have always sequences with alternate elements:
# Var1 Var2 Var3 Var4 Var5 Var6 V7
# 1 b c a b c a bcabca
# 2 c a b c a b cabcab
# 3 a b c a b c abcabc
# 4 b a b c a b babcab
# 5 c b c a b c cbcabc
# 6 a c a b c a acabca
However, the rest of the permutations could have value even if there is one coincidence of elements in like-neighbour restriction. For instance:
# Var1 Var2 Var3 Var4 Var5 Var6 Coincidence
# 1 b b a b c a -->[bb]
# 2 c c b c a b -->[cc]
# 3 a b c a a c -->[aa]
# 4 b a c c a b -->[cc]
Is it possible to use expand.grid for that too?
If it's "only one more", then I suggest the simplest way to allow it is to force it.
Using the start from the previous question:
r <- replicate(6, seq_len(length(abc)-1), simplify=FALSE)
r[[1]] <- c(r[[1]], length(abc))
We now copy this single list (that is passed to expand.grid) and replace each of the 2nd through last elements with 0. Recall that we are using these numbers with cumsum to change from the previous value, so replacing 1:2 with 0 means that we are forcing the next element to be the same.
rs <- lapply(seq_len(length(r)-1) + 1, function(i) { r[[i]] <- 0; r; })
# ^^^^^^^^^^^^^^^^^^^^^^^^ or: seq_len(length(r))[-1]
str(rs[1:2])
# List of 2
# $ :List of 6
# ..$ : int [1:3] 1 2 3
# ..$ : num 0 <--- the second letter will repeat
# ..$ : int [1:2] 1 2
# ..$ : int [1:2] 1 2
# ..$ : int [1:2] 1 2
# ..$ : int [1:2] 1 2
# $ :List of 6
# ..$ : int [1:3] 1 2 3
# ..$ : int [1:2] 1 2
# ..$ : num 0 <--- the third letter will repeat
# ..$ : int [1:2] 1 2
# ..$ : int [1:2] 1 2
# ..$ : int [1:2] 1 2
### other rs's are similar
We can verify that this works as we think it should:
# rs[[1]] repeats the first 2
m <- t(apply(do.call(expand.grid, rs[[1]]), 1, cumsum) %% length(abc) + 1)
m[] <- abc[m]
head(as.data.frame(cbind(m, apply(m, 1, paste, collapse = ""))), n=3)
# Var1 Var2 Var3 Var4 Var5 Var6 V7
# 1 b b c a b c bbcabc
# 2 c c a b c a ccabca
# 3 a a b c a b aabcab
# rs[[3]] repeats the 3rd-4th
m <- t(apply(do.call(expand.grid, rs[[3]]), 1, cumsum) %% length(abc) + 1)
m[] <- abc[m]
head(as.data.frame(cbind(m, apply(m, 1, paste, collapse = ""))), n=3)
# Var1 Var2 Var3 Var4 Var5 Var6 V7
# 1 b c a a b c bcaabc
# 2 c a b b c a cabbca
# 3 a b c c a b abccab
From here, let's automate it by putting all of these into one list and lapplying them.
rs <- c(list(r), rs)
rets <- do.call(rbind.data.frame, c(stringsAsFactors=FALSE, lapply(rs, function(r) {
m <- t(apply(do.call(expand.grid, r), 1, cumsum) %% length(abc) + 1)
m[] <- abc[m]
as.data.frame(cbind(m, apply(m, 1, paste, collapse = "")), stringsAsFactors=FALSE)
})))
head(rets)
# Var1 Var2 Var3 Var4 Var5 Var6 V7
# 1 b c a b c a bcabca
# 2 c a b c a b cabcab
# 3 a b c a b c abcabc
# 4 b a b c a b babcab
# 5 c b c a b c cbcabc
# 6 a c a b c a acabca
tail(rets)
# Var1 Var2 Var3 Var4 Var5 Var6 V7
# 331 b c b a c c bcbacc
# 332 c a c b a a cacbaa
# 333 a b a c b b abacbb
# 334 b a c b a a bacbaa
# 335 c b a c b b cbacbb
# 336 a c b a c c acbacc
Walkthrough of additional steps:
rs <- c(list(r), rs) makes the first (non-repeating r) an enclosed list, then prepends it to the rs list.
lapply(rs, function(r) ...) does the ... from the previous question once for each element in the rs list. I named it r inside the anon-function to make it perfectly clear (inside the function) that each time it gets a new r, it does exactly the same steps as the last question.
do.call(rbind.data.frame, c(stringsAsFactors=FALSE, ... because each return from the lapply will be a data.frame, and we want to combine them into a single frame. I prefer no factors, but you can choose otherwise if you need. (Instead of rbind.data.frame, you could use data.table::rbindlist or dplyr::bind_rows, both without stringsAsFactors.)
Now the first 96 rows have no repeats, then the remaining five batches of 48 rows each (total 336 rows) have one repeat each. We "know" that 48 is the right number for each of the repeat-once lists, since by changing one of the positions from "1 2" possible to "0" (from 2 to 1 possible value) we halve the total number of possible combinations (96 / 2 == 48).
If for some reason your next question asks how to expand this to allow two repeats ... then I wouldn't necessarily recommend brute-forcing this aspect of it: there are 6 or 10 possible combinations (depending on if "aaa" is allowed) of repeats, and I would much prefer to go to a more programmatic handling than this brute-force appending of the one-constraint.

Arranging a 3 dimensional contingency table in R in order to run a Cochran-Mantel-Haenszel analysis?

I am attempting to run a Mantel-Haenszel analysis in R to determine whether or not a comparison of proportions test is still significant when accounting for a 'diagnosis' ratio within groups. This test is available in the stats package.
library(stats)
mantelhaen.test(x)
Having done some reading, I've found that this test can perform an odds ratio test on a contingency table that is n x n x k, as opposed to simply n x n. However, I am having trouble arranging my data in the proper way, as I am fairly new to R. I have created some example data...
ex.label <- c("A","A","A","A","A","A","A","B","B","B")
ex.status <- c("+","+","-","+","-","-","-","+","+","-")
ex.diag <- c("X","X","Z","Y","Y","Y","X","Y","Z","Z")
ex.data <- data.frame(ex.label,ex.diag,ex.status)
Which looks like this...
ex.label ex.diag ex.status
1 A X +
2 A X +
3 A Z -
4 A Y +
5 A Y -
6 A Y -
7 A X -
8 B Y +
9 B Z +
10 B Z -
I was originally able to use a simple N-1 chi-square to run a comparison of proportions test of + to - for only the A and B, but now I want to be able to account for the ex.diag as well. I'll show a graph here for what I wanted to be looking at, which is basically to compare the significance of the ratio in each column. I was able to do this, but I now want to be able to account for ex.diag.
I tried to use the ftable() function to arrange my data in a way that would work.
ex.ftable <- ftable(ex.data)
Which looks like this...
ex.status - +
ex.label ex.diag
A X 1 2
Y 2 1
Z 1 0
B X 0 0
Y 0 1
Z 1 1
However, when I run mantelhaen.test(ex.ftable), I get the error 'x' must be a 3-dimensional array. How can I arrange my data in such a way that I can actually run this test?
In mantelhaen.test the last dimension of the 3-dimensional contingency table x needs to be the stratification variable (ex.diag). This matrix can be generated as follows:
ex.label <- c("A","A","A","A","A","A","A","B","B","B")
ex.status <- c("+","+","-","+","-","-","-","+","+","-")
ex.diag <- c("X","X","Z","Y","Y","Y","X","Y","Z","Z")
# Now ex.diag is in the first column
ex.data <- data.frame(ex.diag, ex.label, ex.status)
# The flat table
( ex.ftable <- ftable(ex.data) )
# ex.status - +
# ex.diag ex.label
# X A 1 2
# B 0 0
# Y A 2 1
# B 0 1
# Z A 1 0
# B 1 1
The 3D matrix can be generated using aperm.
# Trasform the ftable into a 2 x 2 x 3 array
# First dimension: ex.label
# Second dimension: ex.status
# Third dimension: ex.diag
( mtx3D <- aperm(array(t(as.matrix(ex.ftable)),c(2,2,3)),c(2,1,3)) )
# , , 1
#
# [,1] [,2]
# [1,] 1 2
# [2,] 0 0
#
# , , 2
#
# [,1] [,2]
# [1,] 2 1
# [2,] 0 1
#
# , , 3
#
# [,1] [,2]
# [1,] 1 0
# [2,] 1 1
Now the Cochran-Mantel-Haenszel chi-squared test can be performed.
# Cochran-Mantel-Haenszel chi-squared test of the null that
# two nominal variables are conditionally independent in each stratum
#
mantelhaen.test(mtx3D, exact=FALSE)
The results of the test is
Mantel-Haenszel chi-squared test with continuity correction
data: mtx3D
Mantel-Haenszel X-squared = 0.23529, df = 1, p-value = 0.6276
alternative hypothesis: true common odds ratio is not equal to 1
95 percent confidence interval:
NaN NaN
sample estimates:
common odds ratio
Inf
Given the low number of cases, it is preferable to compute an exact conditional test (option exact=TRUE).
mantelhaen.test(mtx3D, exact=T)
# Exact conditional test of independence in 2 x 2 x k tables
#
# data: mtx3D
# S = 4, p-value = 0.5
# alternative hypothesis: true common odds ratio is not equal to 1
# 95 percent confidence interval:
# 0.1340796 Inf
# sample estimates:
# common odds ratio
# Inf

R : How to fill an array with this peculiar calculation?

I would fill an array D with a loop, and only with a loop (please), where my data are structured in this particular way:
A <- data.frame(matrix(nrow=12,ncol=10))
c_2 <- c(0.003,0.004)
an <- sapply(c_2,function(x) x*c(1:12))
B <-array(an,c(12,1,2))
set.seed(1)
C<- rnorm(10,0.6,0.1)
D <- array(NA,c(12,1,20))
f_12 <- exp(c(0:11)/12)
for (k in 1:length(A)){
for (i in 1:dim(B)[3]){
for (z in 1:length(C)){
M_nat <- C[z]
A[,z] <- f_12*M_nat
ris_1 <- A[,k]
cost_1 <- B[,,i]
prov_1 <- cost_1*ris_1
D[,,k*i] <- prov_1
}
}
}
My expected result is an array D, where each [,,z] dimension is a result from B[,,1] and B[,,2] for each column of A(A in the loop).
With the above code the R result is an array where the first ten z dimension are full, and after, some have values, and others are NA. Where did I go wrong?
outer(1:10, 1:2, "*") tells you which indices you can fill with D[,,k*i]:
# [,1] [,2]
# [1,] 1 2
# [2,] 2 4
# [3,] 3 6
# [4,] 4 8
# [5,] 5 10
# [6,] 6 12
# [7,] 7 14
# [8,] 8 16
# [9,] 9 18
#[10,] 10 20
These are the ones not possible:
(1:20)[!(1:20 %in% outer(1:10, 1:2, "*"))]
#[1] 11 13 15 17 19
And indeed, those elements are not filled in D. Note that you filled some elements more than once.
You could use (i-1) * 10 + k instead if k*i.

How to find a row or column under specific conditions in a matrix in R [duplicate]

This question already has answers here:
given value of matrix, getting it's coordinate
(2 answers)
Closed 7 years ago.
For example, if we have a matrix or say array with the following format
How can we find the index of rows or columns which only have numbers between 10 to 20 inside ?
M = array(c(1,1,12,34,0,19,15,1,0,17,12,0,21,1,11,1), dim=c(4,4))
And, also, I am not allowed to use for or while loops to do this.
Another thing is that the matrix or array may have a more than 2 dimensions. if the method can also apply to multi-dimensional matrix or array, it will be better for me. Thanks.
Instead of trying to find the index of qualified single elements, I need to find those rows or columns in which all the elements are between the interval.
In this example, I hope to have a result telling me that Row number 3 is a row that all the numbers within this row are between 10 to 20.
Use which(..., arr.ind = TRUE). Here I assume between means 10 and 20 are non-inclusive
which(M > 10 & M < 20, arr.ind = TRUE)
# row col
# [1,] 3 1
# [2,] 2 2
# [3,] 3 2
# [4,] 2 3
# [5,] 3 3
# [6,] 3 4
This will also work on 3-dimensional arrays (and higher).
## Three dimensions
dim(M) <- c(2, 4, 2)
which(M > 10 & M < 20, arr.ind = TRUE)
# dim1 dim2 dim3
# [1,] 1 2 1
# [2,] 2 3 1
# [3,] 1 4 1
# [4,] 2 1 2
# [5,] 1 2 2
# [6,] 1 4 2
## Four dimensions
dim(M) <- rep(2, 4)
which(M > 10 & M < 20, arr.ind = TRUE)
# dim1 dim2 dim3 dim4
# [1,] 1 2 1 1
# [2,] 2 1 2 1
# [3,] 1 2 2 1
# [4,] 2 1 1 2
# [5,] 1 2 1 2
# [6,] 1 2 2 2
## ... and so on
Note: To include 10 and 20, just use M >= 10 & M <= 20
Data:
M <- structure(c(1, 1, 12, 34, 0, 19, 15, 1, 0, 17, 12, 0, 21, 1,
11, 1), .Dim = c(4L, 4L))
Update: From your edit, you can find the row numbers for which all values are between 10 and 20 with
which(rowSums(M >= 10 & M <= 20) == ncol(M))
# [1] 3

storing value against variable name "QW1I5K20" in an array element Q[1,5,20] using R

I have an excel file (.csv) with a sorted column of variable names such as "QW1I1K5" and numerical values against them.
this list goes on for
W from 1 to 15
I from 1 to 4
K from 1 to 30
total elements = 15*4*30 = 1800
I want to store the numerical values against these variables in an array whose indices are derived from the variable name .
for example QW1I1K5 has a value 11 . this must be stored in an array element Q[1,1,5] = 11 ( index set of [1,1,5] corresponds to W1 , I1 , K5)
May be this helps
Q <- array(dat$Col2, dim=c(15,4,30))
dat$Col2[dat$Col1=='QW1I1K5']
#[1] 34
Q[1,1,5]
#[1] 34
dat$Col2[dat$Col1=='QW4I3K8']
#[1] 38
Q[4,3,8]
#[1] 38
If you want the index along with the values
library(reshape2)
d1 <- melt(Q)
head(d1,3)
# Var1 Var2 Var3 value
#1 1 1 1 12
#2 2 1 1 9
#3 3 1 1 29
Q[1,1,1]
#[1] 12
Q[3,1,1]
#[1] 29
Update
Suppose, your data is in the order as you described in the comments, which will be dat1
indx <- read.table(text=gsub('[^0-9]+', ' ', dat1$Col1), header=FALSE)
dat2 <- dat1[do.call(order, indx[,3:1]),]
Q1 <- array(dat2$Col2,dim=c(15,4,30))
Q1[1,1,2]
#[1] 20
dat2$Col2[dat2$Col1=='QW1I1K2']
#[1] 20
data
Col1 <- do.call(paste,c(expand.grid('QW', 1:15, 'I', 1:4, 'K',1:30),
list(sep='')))
set.seed(24)
dat <- data.frame(Col1, Col2=sample(1:40, 1800,replace=TRUE))
dat1 <- dat[order(as.numeric(gsub('[^0-9]+', '', dat$Col1))),]
row.names(dat1) <- NULL
I would suggest looking at using "data.table" and setting your key to the split columns. You can use cSplit from my "splitstackshape" function to easily split the column.
Sample Data:
df <- data.frame(
V1 = c("QW1I1K1", "QW1I1K2", "QW1I1K3",
"QW1I1K4", "QW2I1K5", "QW2I3K2"),
V2 = c(15, 20, 5, 6, 7, 9))
df
# V1 V2
# 1 QW1I1K1 15
# 2 QW1I1K2 20
# 3 QW1I1K3 5
# 4 QW1I1K4 6
# 5 QW2I1K5 7
# 6 QW2I3K2 9
Splitting the column:
library(splitstackshape)
out <- cSplit(df, "V1", "[A-Z]+", fixed = FALSE)
setnames(out, c("V2", "W", "I", "K"))
setcolorder(out, c("W", "I", "K", "V2"))
setkey(out, W, I, K)
out
# W I K V2
# 1: 1 1 1 15
# 2: 1 1 2 20
# 3: 1 1 3 5
# 4: 1 1 4 6
# 5: 2 1 5 7
# 6: 2 3 2 9
Extracting rows:
out[J(1, 1, 4)]
# W I K V2
# 1: 1 1 4 6
out[J(2, 3, 2)]
# W I K V2
# 1: 2 3 2 9

Resources