I have a vector with five items.
my_vec <- c("a","b","a","c","d")
If I want to re-arrange those values into a new vector (shuffle), I could use sample():
shuffled_vec <- sample(my_vec)
Easy - but the sample() function only gives me one possible shuffle. What if I want to know all possible shuffling combinations? The various "combn" functions don't seem to help, and expand.grid() gives me every possible combination with replacement, when I need it without replacement. What's the most efficient way to do this?
Note that in my vector, I have the value "a" twice - therefore, in the set of shuffled vectors returned, they all should each have "a" twice in the set.
I think permn from the combinat package does what you want
library(combinat)
permn(my_vec)
A smaller example
> x
[1] "a" "a" "b"
> permn(x)
[[1]]
[1] "a" "a" "b"
[[2]]
[1] "a" "b" "a"
[[3]]
[1] "b" "a" "a"
[[4]]
[1] "b" "a" "a"
[[5]]
[1] "a" "b" "a"
[[6]]
[1] "a" "a" "b"
If the duplicates are a problem you could do something similar to this to get rid of duplicates
strsplit(unique(sapply(permn(my_vec), paste, collapse = ",")), ",")
Or probably a better approach to removing duplicates...
dat <- do.call(rbind, permn(my_vec))
dat[duplicated(dat),]
Noting that your data is effectively 5 levels from 1-5, encoded as "a", "b", "a", "c", and "d", I went looking for ways to get the permutations of the numbers 1-5 and then remap those to the levels you use.
Let's start with the input data:
my_vec <- c("a","b","a","c","d") # the character
my_vec_ind <- seq(1,length(my_vec),1) # their identifier
To get the permutations, I applied the function given at Generating all distinct permutations of a list in R:
permutations <- function(n){
if(n==1){
return(matrix(1))
} else {
sp <- permutations(n-1)
p <- nrow(sp)
A <- matrix(nrow=n*p,ncol=n)
for(i in 1:n){
A[(i-1)*p+1:p,] <- cbind(i,sp+(sp>=i))
}
return(A)
}
}
First, create a data.frame with the permutations:
tmp <- data.frame(permutations(length(my_vec)))
You now have a data frame tmp of 120 rows, where each row is a unique permutation of the numbers, 1-5:
>tmp
X1 X2 X3 X4 X5
1 1 2 3 4 5
2 1 2 3 5 4
3 1 2 4 3 5
...
119 5 4 3 1 2
120 5 4 3 2 1
Now you need to remap them to the strings you had. You can remap them using a variation on the theme of gsub(), proposed here: R: replace characters using gsub, how to create a function?
gsub2 <- function(pattern, replacement, x, ...) {
for(i in 1:length(pattern))
x <- gsub(pattern[i], replacement[i], x, ...)
x
}
gsub() won't work because you have more than one value in the replacement array.
You also need a function you can call using lapply() to use the gsub2() function on every element of your tmp data.frame.
remap <- function(x,
old,
new){
return(gsub2(pattern = old,
replacement = new,
fixed = TRUE,
x = as.character(x)))
}
Almost there. We do the mapping like this:
shuffled_vec <- as.data.frame(lapply(tmp,
remap,
old = as.character(my_vec_ind),
new = my_vec))
which can be simplified to...
shuffled_vec <- as.data.frame(lapply(data.frame(permutations(length(my_vec))),
remap,
old = as.character(my_vec_ind),
new = my_vec))
.. should you feel the need.
That gives you your required answer:
> shuffled_vec
X1 X2 X3 X4 X5
1 a b a c d
2 a b a d c
3 a b c a d
...
119 d c a a b
120 d c a b a
Looking at a previous question (R: generate all permutations of vector without duplicated elements), I can see that the gtools package has a function for this. I couldn't however get this to work directly on your vector as such:
permutations(n = 5, r = 5, v = my_vec)
#Error in permutations(n = 5, r = 5, v = my_vec) :
# too few different elements
You can adapt it however like so:
apply(permutations(n = 5, r = 5), 1, function(x) my_vec[x])
# [,1] [,2] [,3] [,4]
#[1,] "a" "a" "a" "a" ...
#[2,] "b" "b" "b" "b" ...
#[3,] "a" "a" "c" "c" ...
#[4,] "c" "d" "a" "d" ...
#[5,] "d" "c" "d" "a" ...
Related
It is straight forward to obtain unique values of a column using unique. However, I am looking to do the same but for multiple columns in a dataframe and store them in a list, all using base R. Importantly, it is not combinations I need but simply unique values for each individual column. I currently have the below:
# dummy data
df = data.frame(a = LETTERS[1:4]
,b = 1:4)
# for loop
cols = names(df)
unique_values_by_col = list()
for (i in cols)
{
x = unique(i)
unique_values_by_col[[i]] = x
}
The problem comes when displaying unique_values_by_col as it shows as empty. I believe the problem is i is being passed to the loop as a text not a variable.
Any help would be greatly appreciated. Thank you.
Why not avoid the for loop altogether using lapply:
lapply(df, unique)
Resulting in:
> $a
> [1] A B C D
> Levels: A B C D
> $b
> [1] 1 2 3 4
Or you have also apply that is specifically done to be run on column or line:
apply(df,2,unique)
result:
> apply(df,2,unique)
a b
[1,] "A" "1"
[2,] "B" "2"
[3,] "C" "3"
[4,] "D" "4"
thought if you want a list lapply return you a list so may be better
Your for loop is almost right, just needs one fix to work:
# for loop
cols = names(df)
unique_values_by_col = list()
for (i in cols) {
x = unique(df[[i]])
unique_values_by_col[[i]] = x
}
unique_values_by_col
# $a
# [1] A B C D
# Levels: A B C D
#
# $b
# [1] 1 2 3 4
i is just a character, the name of a column within df so unique(i) doesn't make sense.
Anyhow, the most standard way for this task is lapply() as shown by demirev.
Could this be what you're trying to do?
Map(unique,df)
Result:
$a
[1] A B C D
Levels: A B C D
$b
[1] 1 2 3 4
This question already has an answer here:
Is there anything wrong with using T & F instead of TRUE & FALSE?
(1 answer)
Closed 4 years ago.
Given a matrix with one row, one column, or one cell, I need to reorder the rows while keeping the matrix structure. I tried adding drop=F but it doesn't work! What did I do?
test = matrix(letters[1:5]) # is a matrix
test[5:1,,drop=F] # not a matrix
test2 = matrix(letters[1:5],nrow=1) # is a matrix
test2[1:1,,drop=F] # not a matrix
test3 = matrix(1) # is a matrix
test3[1:1,,drop=F] # not a matrix
I'd guess it was an overwritten F; F can be set as a variable, in which case it's no longer false. Always write out FALSE fully, it can't be set as a variable.
See Is there anything wrong with using T & F instead of TRUE & FALSE?
Also the R Inferno, section 8.1.32, is a good reference.
> F <- 1
> test = matrix(letters[1:5]) # is a matrix
> test[5:1,,drop=F] # not a matrix
[1] "e" "d" "c" "b" "a"
> test[5:1,,drop=FALSE] # but this is a matrix
[,1]
[1,] "e"
[2,] "d"
[3,] "c"
[4,] "b"
[5,] "a"
> rm(F)
> test[5:1,,drop=F] # now a matrix again
[,1]
[1,] "e"
[2,] "d"
[3,] "c"
[4,] "b"
[5,] "a"
The code in your question works fine in a fresh R session:
test = matrix(letters[1:5]) # is a matrix
result = test[5:1,,drop=F]
result
# [,1]
# [1,] "e"
# [2,] "d"
# [3,] "c"
# [4,] "b"
# [5,] "a"
class(result) # still a matrix
# [1] "matrix"
dim(result)
# [1] 5 1
Even on the 1x1 matrix:
test3 = matrix(1) # is a matrix
result3 = test3[1:1,,drop=F]
class(result3)
# [1] "matrix"
dim(result3)
# [1] 1 1
Maybe you've loaded other packages that are overriding the default behavior? What makes you think you don't end up with a matrix?
The following works:
test <- matrix(test[5:1,, drop = F], nrow = 5, ncol = 1)
When you use is.matrix to test it, the output is a matrix. At the same time, you specify the number of rows (nrow) and number of columns (ncol) to coerce it to the number of rows and columns you require.
I would like to sort the data x (here: 1:12) according to the sectors sec and subsectors ssec. Below is an example showing how this can be done. The questions is whether this can be done more elegantly (maybe with a base-R function/not using additional packages)?
## Data
set.seed(17)
(sec <- sample(rep(LETTERS[1:3], each = 4))) # 3 sectors
(ssec <- rep(sample(1:4, 12, replace = TRUE))) # 4 subsectors
x <- 1:12 # data to sort according to increasing sectors and subsectors
## Sort according to sectors
ord <- order(sec)
x. <- x[ord]
sec. <- sec[ord]
ssec. <- ssec[ord]
## Sort according to subsectors
usec. <- unique(sec.)
x.. <- x.
ssec.. <- ssec.
for(grp in usec.) {
ii <- sec. == grp # indices of components in that sector
ord. <- order(ssec.[ii])
x..[ii] <- x.[ii][ord.]
ssec..[ii] <- ssec.[ii][ord.]
}
## Result
x..
sec.
ssec..
The order function from base R also accepts multiple arguments. From ?order:
order returns a permutation which rearranges its first argument into
ascending or descending order, breaking ties by further arguments.
To demonstrate, we can check how order(sec, ssec) sort the sector and subsector
Here is the original sec and ssec:
sec
[1] "B" "C" "A" "B" "A" "B" "C" "C" "C" "A" "B" "A"
ssec
[1] 3 1 3 2 1 2 4 1 3 2 1 4
After applying the ordered index, sec is sorted alphabetically and ssec is sorted within each sec, which means the index order(sec, ssec) is the sorting index expected:
sec[order(sec, ssec)]
[1] "A" "A" "A" "A" "B" "B" "B" "B" "C" "C" "C" "C"
ssec[order(sec, ssec)]
[1] 1 2 3 4 1 2 2 3 1 1 3 4
I am trying to generate a random sequence from a fixed number of characters that contains at least one of each character.
For example having the ensemble
m = letters[1:3]
I would like to create a sequence of N = 10 elements that contain at least one of each m characters, like
a
a
a
a
b
c
c
c
c
a
I tried with sample(n,N,replace=T) but in this way also a sequence like
a
a
a
a
a
c
c
c
c
a
can be generated that does not contain b.
f <- function(x, n){
sample(c(x, sample(m, n-length(x), replace=TRUE)))
}
f(letters[1:3], 5)
# [1] "a" "c" "a" "b" "a"
f(letters[1:3], 5)
# [1] "a" "a" "b" "b" "c"
f(letters[1:3], 5)
# [1] "a" "a" "b" "c" "a"
f(letters[1:3], 5)
# [1] "b" "c" "b" "c" "a"
Josh O'Briens answer is a good way to do it but doesn't provide much input checking. Since I already wrote it might as well present my answer. It's pretty much the same thing but takes care of checking things like only considering unique items and making sure there are enough unique items to guarantee you get at least one of each.
at_least_one_samp <- function(n, input){
# Only consider unique items.
items <- unique(input)
unique_items_count <- length(items)
if(unique_items_count > n){
stop("Not enough unique items in input to give at least one of each")
}
# Get values for vector - force each item in at least once
# then randomly select values to get the remaining.
vals <- c(items, sample(items, n - unique_items_count, replace = TRUE))
# Now shuffle them
sample(vals)
}
m <- c("a", "b", "c")
at_least_one_samp(10, m)
in R I have produced the following list L:
>L
[[1]]
[1] "A" "B" "C"
[[2]]
[1] "D"
[[3]]
[1] NULL
I would like to manipulate the list L arriving at a database df like
>df
df[,1] df[,2]
"A" 1
"B" 1
"C" 1
"D" 2
where the 2nd column gives the position in the list L of the corresponding element in column 1.
My question is: is(are) there a() built-in R function(s) which can do this manipulation quickly? I can do it using "brute force", but my solution does not scale well when I consider much bigger lists.
I thank you all!
You'll get a warning because of your NULL value, but you can use stack if you give your list items names:
L <- list(c("A", "B", "C"), "D", NULL)
stack(setNames(L, seq_along(L)))
# values ind
# 1 A 1
# 2 B 1
# 3 C 1
# 4 D 2
# Warning message:
# In stack.default(setNames(L, seq_along(L))) :
# non-vector elements will be ignored
If the warning displeases you, you can, of course, run stack on the non-NULL elements, but do it after you name your list elements so that the "ind" column reflects the correct value.
I'll show in 2 steps just for clarity:
names(L) <- seq_along(L)
stack(L[!sapply(L, is.null)])
Similarly, if you've gotten rid of the NULL list elements, you can use melt from "reshape2". You don't gain anything in brevity, and I'm not sure that you gain anything in efficiency either, but I thought I'd share it as an option.
library(reshape2)
names(L) <- seq_along(L)
melt(L[!sapply(L, is.null)])
Ananda's answer is seemingly better than this, but I'll put it up anyway:
> cbind(unlist(L), rep(1:length(L), sapply(L, length)))
[,1] [,2]
[1,] "A" "1"
[2,] "B" "1"
[3,] "C" "1"
[4,] "D" "2"