From ripley at stats.ox.ac.uk  Fri Apr 14 21:29:04 2006
From: ripley at stats.ox.ac.uk (Prof Brian Ripley)
Date: Fri, 14 Apr 2006 20:29:04 +0100 (BST)
Subject: [Rd] row.names in data.frame
Message-ID: <Pine.LNX.4.64.0604141959160.3266@gannet.stats.ox.ac.uk>

We know from the White Book p.57 that the row names of a data frame `are 
never NULL and must be unique'.  R documents that row.names() returns a 
character vector, and in R (much more so than on S) a long character 
vector of short unique strings is expensive to store (I saw 72 bytes/row 
on a 64-bit machine for 1:1e6).  [Incidentally, in the White Book the 
index page nos are all off by one for this item, and commonly elsewhere. 
It seems to be LaTeX indexing the page on which a para finishes.]

Last time this came up Martin Maechler asked if we could not do it more 
efficiently, and reminded us recently.  It would be fairly easy if 
everyone used the row.names() and row.names<-() accessor functions, but 
some packages (notably Design and Hmisc) access the attribute "row.names" 
directly (and what that is seems to be undocumented).

I noticed that the White Book does not appear to say that the row names 
are character, and indeed says

   'If all else fails the row names are just the row numbers.'

and it seems the author of expand.grid() took that literally, for it used 
to assign integers to the row names.  However, the current S-PLUS help for 
both row.names and data.frame say row names are a character vector (and 
that row.names<-() coerces to character).

We can certainly differentiate between the internal representation and the 
the result of row.names().  Here is my idea:

1) The internal representation is either NULL, an integer vector or a 
character vector.

2) attr(x, "row.names") will always return either an integer vector or a 
character vector, using 1:nrow(x) if the internal representation is NULL.

3) row.names() will always return as.character(attr(x, "row.names)).

4) attr<- and row.names<- can set NULL, integer or character.

5) Row-indexing a data frame with NULL or integer representation will give 
an integer representation.

This would appear to be completely back-compatible for those who only work 
via the accessor functions, and probably work with almost all package code 
that manipulates attributes directly.  Since the changes can be done 
almost entirely in C code, the performance hit should be negliglible.

The benefits will probably only be appreciable with `tall and skinny'
data frames, as even 72 bytes per row is only going to buy you 9 numeric 
columns.  But that is it seems a common enough case to make this 
worthwhile.

This would be a change aimed at 2.4.0, since we would need plenty of time 
both for testing and to alter code to make use of the more efficient 
representations.

BTW, the maximum object length of 2^31 - 1 ensures that an integer 
representation of row numbers suffices.

-- 
Brian D. Ripley,                  ripley at stats.ox.ac.uk
Professor of Applied Statistics,  http://www.stats.ox.ac.uk/~ripley/
University of Oxford,             Tel:  +44 1865 272861 (self)
1 South Parks Road,                     +44 1865 272866 (PA)
Oxford OX1 3TG, UK                Fax:  +44 1865 272595



[Rd] row.names in data.frame
Prof Brian Ripley