55

I have a logical vector, for which I wish to insert new elements at particular indexes. I've come up with a clumsy solution below, but is there a neater way?

probes <- rep(TRUE, 15)
ind <- c(5, 10)
probes.2 <- logical(length(probes)+length(ind))
probes.ind <- ind + 1:length(ind)
probes.original <- (1:length(probes.2))[-probes.ind]
probes.2[probes.ind] <- FALSE
probes.2[probes.original] <- probes

print(probes)

gives

[1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE

and

print(probes.2)

gives

[1]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE
[13]  TRUE  TRUE  TRUE  TRUE  TRUE

So it works but is ugly looking - any suggestions?

Henrik
  • 65,555
  • 14
  • 143
  • 159
Aaron Statham
  • 2,048
  • 1
  • 15
  • 16
  • Why do you need to do the inserting? – hadley Sep 30 '09 at 03:41
  • long story, but basically looking for runs of TRUE's, but have predetermined places where I want to break up a run. First time i used "rle" but it scaled very poorly, so came up with this dirty vector solution – Aaron Statham Sep 30 '09 at 07:49

9 Answers9

90

These are all very creative approaches. I think working with indexes is definitely the way to go (Marek's solution is very nice).

I would just mention that there is a function to do roughly that: append().

probes <- rep(TRUE, 15)
probes <- append(probes, FALSE, after=5)
probes <- append(probes, FALSE, after=11)

Or you could do this recursively with your indexes (you need to grow the "after" value on each iteration):

probes <- rep(TRUE, 15)
ind <- c(5, 10)
for(i in 0:(length(ind)-1)) 
    probes <- append(probes, FALSE, after=(ind[i+1]+i))

Incidentally, this question was also previously asked on R-Help. As Barry says:

"Actually I'd say there were no ways of doing this, since I dont think you can actually insert into a vector - you have to create a new vector that produces the illusion of insertion!"

Shane
  • 98,550
  • 35
  • 224
  • 217
  • I didn't know about the append function, thanks for bringing it to my attention! I should've specified in my question that I didn't want a loop because my real vectors will have thousands/millions of elements – Aaron Statham Sep 30 '09 at 03:23
  • 1
    And one can set `after = 0` to insert a new value at the start of a vector. – coip Aug 11 '16 at 22:05
  • If `ind` is sorted, as you seem to assume, can you do the loop more simply in reverse: `for(i in length(ind):1) append(..., after=ind[i])` ? – Yaakov Baruch Jan 05 '17 at 04:11
45

You can do some magic with indexes:

First create vector with output values:

probs <- rep(TRUE, 15)
ind <- c(5, 10)
val <- c( probs, rep(FALSE,length(ind)) )
# > val
#  [1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
# [13]  TRUE  TRUE  TRUE FALSE FALSE

Now trick. Each old element gets rank, each new element gets half-rank

id  <- c( seq_along(probs), ind+0.5 )
# > id
#  [1]  1.0  2.0  3.0  4.0  5.0  6.0  7.0  8.0  9.0 10.0 11.0 12.0 13.0 14.0 15.0
# [16]  5.5 10.5

Then use order to sort in proper order:

val[order(id)]
#  [1]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE
# [13]  TRUE  TRUE  TRUE  TRUE  TRUE
Marek
  • 49,472
  • 15
  • 99
  • 121
12
probes <- rep(TRUE, 1000000)
ind <- c(50:100)
val <- rep(FALSE,length(ind))

new.probes <- vector(mode="logical",length(probes)+length(val))
new.probes[-ind] <- probes
new.probes[ind] <- val

Some timings: My method user system elapsed 0.03 0.00 0.03

Marek method user system elapsed 0.18 0.00 0.18

R append with for loop user system elapsed 1.61 0.48 2.10

Wojciech Sobala
  • 7,431
  • 2
  • 21
  • 27
  • 2
    Great idea, seems to be the most efficient way of doing this! However, you need to shift all indices in order to get this to work as intended, i.e. add `ind <- ind + 0:(length(ind)-1)` before doing assignments to `new.probes`. – aoles Sep 13 '16 at 14:23
  • 1
    You can omit the last step (and definition of `val`) by just initializing `new.probes` with values to replace: `new.probes <- rep(FALSE,length(probes)+length(val))`. That makes it even faster. – Davor Josipovic Jun 21 '17 at 08:52
  • 1
    -1 It would be great answer if it was correct. You need `ind <- ind + seq_len(length(ind))` (`1:length(ind)`, not from 0 as in first comment). And `logical(length(probes)+length(val))` or `rep(FALSE, length(probes)+length(val))` is less cryptic than `vector(mode=...` one. – Marek Jul 22 '20 at 08:32
8

How about this:

> probes <- rep(TRUE, 15)
> ind <- c(5, 10)

> probes.ind <- rep(NA, length(probes))
> probes.ind[ind] <- FALSE
> new.probes <- as.vector(rbind(probes, probes.ind))
> new.probes <- new.probes[!is.na(new.probes)]
> new.probes
 [1]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE
[13]  TRUE  TRUE  TRUE  TRUE  TRUE
Jonathan Chang
  • 24,567
  • 5
  • 34
  • 33
  • 5
    Sneaky! Uses the fact that as.vector does a column-wise collapse of the matrix created by rbind. This is because a matrix is just a vector with additional information that indicates the number of rows, and is internally stored in column-wise order. This is part of the R language definition, but might be a bit obscure, depending on who's reading the code... – Harlan Sep 29 '09 at 19:16
  • @Harlan Great explanation. That make a big difference when working through this example. – Rob Oct 10 '12 at 17:43
2

That is sorta tricky. Here's one way. It iterates over the list, inserting each time, so it's not too efficient.

probes <- rep(TRUE, 15)
probes.ind <- ind + 0:(length(ind)-1)
for (i in probes.ind) {
  probes <- c(probes[1:i], FALSE, probes[(i+1):length(probes)])
}

> probes
 [1]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE
[13]  TRUE  TRUE  TRUE  TRUE  TRUE

This should even work if ind has repeated elements, although ind does need to be sorted for the probes.ind construction to work.

Harlan
  • 18,883
  • 8
  • 47
  • 56
1

Or you can do it using the insertRow function from the miscTools package.

probes <- rep(TRUE, 15)
ind <- c(5,10)
for (i in ind){
    probes <- as.vector(insertRow(as.matrix(probes), i, FALSE))
}
unknown
  • 853
  • 1
  • 10
  • 23
  • I got here looking for that. The tricks about using the names to sort by are cool and deepen my understanding of the language, but this is better for my situation. – Chris Feb 13 '18 at 16:44
1

Using the solution from Wojciech Sobala, I made a generalized function which would work with any data mode:

insertValuesAtIdx <- function(vec,values,idx)
{
  res<-vector(mode=mode(vec),length = length(vec)+length(idx))
  res[-idx]<-vec
  res[idx]<-values
  return(res)
}
insertValuesAtIdx(LETTERS,"a",c(4,6))
insertValuesAtIdx(1:100,c(1000,1001,1002),c(4,6,56))
Marius
  • 11
  • 2
0

I came up with a good answer that's easy to understand and fairly fast to run, building off Wojciech's answer above. I'll adapt the method for the example here, but it can be easily generalized to pretty much any data type for an arbitrary pattern of missing points (shown below).

probes <- rep(TRUE, 15)
ind <- c(5,10)

probes.final <- rep(FALSE, length(probes)+length(ind))
probes.final[-ind] <- probes

The data I needed this for is sampled at a regular interval, but many samples are thrown out, and the resulting data file only includes the timestamps and measurements for those retained. I needed to produce a vector containing all the timestamps and a data vector with NAs inserted for timestamps that were tossed. I used the "not in" function stolen from here to make it a bit simpler.

`%notin%` <- Negate(`%in%`)
dat <- rnorm(50000) # Data given
times <- seq(from=554.3, by=0.1, length.out=70000] # "Original" time stamps
times <- times[-sample(2:69999, 20000)] # "Given" times with arbitrary points missing from interior

times.final <- seq(from=times[1], to=times[length(times)], by=0.1)
na.ind <- which(times.final %notin% times)
dat.final <- rep(NA, length(times.final))
dat.final[-na.ind] <- dat
Ben
  • 459
  • 4
  • 16
0

Um, hi, I had the same doubt, but I couldn't understand what people had answered, because I'm still learning the language. So I tried make my own and I suppose it works! I created a vector and I wanted to insert the value 100 after the 3rd, 5th and 6th indexes. This is what I wrote.

vector <-  c(0:9)
indexes <-  c(6, 3, 5)
indexes <- indexes[order(indexes)]
i <-  1
j <-  0

while(i <= length(indexes)){
  vector <- append(vector, 100, after = indexes[i] + j)
  i <-i + 1
  j <- j + 1
}
vector

The vector "indexes" must be in ascending order for this to work. This is why I put them in order at the third line. The variable "j" is necessary because at each iteration, the length of the new vector increases and the original values are moved. In the case you wish to insert the new value next to each other, simply repeat the number of the index. For instance, by assigning indexes <- c(3, 5, 5, 5, 6), you should get vector == 0 1 2 100 3 4 100 100 100 5 100 6 7 8 9

Paulo
  • 1
  • 1