60

Would someone please explain to me the correct usage of .I for returning the row numbers of a data.table?

I have data like this:

require(data.table)
DT <- data.table(X=c(5, 15, 20, 25, 30))
DT
#     X
# 1:  5
# 2: 15
# 3: 20
# 4: 25
# 5: 30

I want to return a vector of row indices where a condition in i is TRUE, e.g. which rows have an X greater than 20.

DT[X > 20]
# rows 4 & 5 are greater than 20

To get the indices, I tried:

DT[X > 20, .I]
# [1] 1 2 

...but clearly I am doing it wrong, because that simply returns a vector containing 1 to the number of returned rows. (Which I thought was pretty much what .N was for?).

Sorry if this seems extremely basic, but all I have been able to find in the data.table documentation is WHAT .I and .N do, not HOW to use them.

user3351605
  • 1,271
  • 3
  • 19
  • 30

3 Answers3

86

If all you want is the row numbers rather than the rows themselves, then use which = TRUE, not .I.

DT[X > 20, which = TRUE]
# [1] 4 5

That way you get the benefits of optimization of i, for example fast joins or using an automatic index. The which = TRUE makes it return early with just the row numbers.

Here's the manual entry for the which argument inside data.table :

TRUE returns the row numbers of x that i matches to. If NA, returns the row numbers of i that have no match in x. By default FALSE and the rows in x that match are returned.


Explanation:

Notice there is a specific relationship between .I and the i = .. argument in DT[i = .., j = .., by = ..] Namely, .I is a vector of row numbers of the subsetted table.

### Lets create some sample data
set.seed(1)
LL <- sample(LETTERS[1:5], 20, TRUE)
DT <- data.table(X=LL)

look at the difference between subsetting the whole table, and subsetting just .I

DT[X == "B", .I]
# [1] 1 2 3 4 5 6

DT[  , .I[X == "B"] ]
# [1]  1  2  5 11 14 19
Henrik
  • 65,555
  • 14
  • 143
  • 159
Ricardo Saporta
  • 54,400
  • 17
  • 144
  • 178
  • Thank you very much! In hindsight it makes a lot of sense now. – user3351605 Mar 14 '14 at 15:21
  • 1
    Thanks for the helpful example. Is it correct to think of this as an order of operations issue? With `DT[i=.., j=..,` the `i` operation happens first, right? When `j` is evaluated, it's working on the subsetted table and the original row numbers are "lost". – dnlbrky Aug 01 '14 at 14:23
  • 9
    Nowadays you can do `DT[X > 10, which = TRUE]` – Rich Scriven Jan 12 '16 at 21:40
  • 5
    `DT[X > 20, which = TRUE]` ... What other secrets are there regarding data.table? I would like knowing them *all*. data.table is the best piece of software I have ever seen. – feinmann Sep 26 '18 at 09:44
  • 1
    @mattdowle ^^^ :) – Ricardo Saporta Oct 08 '18 at 19:14
19

Sorry if this seems extremely basic, but all I have been able to find in the data.table documentation is WHAT .I and .N do, not HOW to use them.

First let's check the documentation. I typed ?data.table and searched for .I. Here's what's there :

Advanced: When grouping, symbols .SD, .BY, .N, .I and .GRP may be used in the j expression, defined as follows.

.I is an integer vector equal to seq_len(nrow(x)). While grouping, it holds for each item in the group its row location in x. This is useful to subset in j; e.g. DT[, .I[which.max(somecol)], by=grp].

Emphasis added by me here. The original intention was for .I to be used while grouping. Note that there is in fact an example there in the documentation of HOW to use .I.

You aren't grouping.

That said, what you tried was reasonable. Over time these symbols have become to be used when not grouping as well. There might be a case that .I should return what you expected. I can see that using .I in j together with both i and by could be useful. Currently .I doesn't seem helpful when i is present, as you pointed out.

Using the which() function is good but might then circumvent optimization in i (which() needs a long logical input which has to be created and passed to it). Using the which=TRUE argument is good but then just returns the row numbers (you couldn't then do something with those row numbers in j by group).

Feature request #1494 filed to discuss changing .I to work the way you expected. The documentation does contain the words "its row location in x" which would imply what you expected since x is the whole data.table.

Matt Dowle
  • 58,872
  • 22
  • 166
  • 224
7

Alternatively,

 DataTable[ , which(X>10) ]

is probably easier to understand and more idiomatically R.

C8H10N4O2
  • 18,312
  • 8
  • 98
  • 134