/
immutable.r
112 lines (99 loc) · 2.61 KB
/
immutable.r
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#' Construct an immutable data frame.
#'
#' An immutable data frame works like an ordinary data frame, except that when
#' you subset it, it returns a reference to the original data frame, not a
#' a copy. This makes subsetting substantially faster and has a big impact
#' when you are working with large datasets with many groups.
#'
#' This method is still a little experimental, so please let me know if you
#' run into any problems.
#'
#' @param df a data frame
#' @return an immutable data frame
#' @S3method "[" idf
#' @S3method names idf
#' @S3method dim idf
#' @S3method as.data.frame idf
#' @S3method "[[" idf
#' @keywords manip
#' @export
#' @examples
#' system.time(dlply(baseball, "id", nrow))
#' system.time(dlply(idata.frame(baseball), "id", nrow))
idata.frame <- function(df) {
self <- new.env()
self$`_data` <- df
self$`_rows` <- seq_len(nrow(df))
self$`_cols` <- names(df)
for (name in names(df)) {
f <- eval(substitute(function(v) {
if (missing(v)) {
`_data`[[name]]
} else {
stop("Immutable")
}
}, list(name = name)))
environment(f) <- self
makeActiveBinding(name, f, self)
}
structure(self,
class = c("idf", "environment"))
}
"[.idf" <- function(x, i, j, drop = TRUE) {
# Single column special cases
if (nargs() == 2) {
j <- i
i <- TRUE
drop <- FALSE
}
if (!missing(j) && length(j) == 1 && drop) {
if (missing(i)) i <- TRUE
return(x[[j]][i])
}
# New rows
rows <- x$`_rows`
if (!missing(i)) {
if (is.character(i)) stop("Row names not supported")
rows <- rows[i]
}
# New cols
cols <- x$`_cols`
if (!missing(j)) {
if (is.character(j)) {
cols <- intersect(cols, j)
} else {
cols <- cols[j]
}
}
# Make active bindings for functions like lm and eval that will treat this
# object as an environment or list
self <- new.env(parent = parent.env(x))
self$`_rows` <- rows
self$`_cols` <- cols
self$`_data` <- x$`_data`
for (col in cols) {
f <- eval(substitute(function(v) {
if (missing(v)) {
`_data`[[name]][`_rows`]
} else {
stop("Immutable")
}
}, list(name = col)))
environment(f) <- self
makeActiveBinding(col, f, self)
}
structure(self,
class = c("idf", "environment"))
}
names.idf <- function(x) x$`_cols`
dim.idf <- function(x) c(length(x$`_rows`), length(x$`_cols`))
as.data.frame.idf <- function(x, ...) {
x$`_data`[x$`_rows`, x$`_cols`]
}
"[[.idf" <- function(x, i) {
if (is.numeric(i)) {
i <- names(x)[i]
}
x$`_data`[[i]][x$`_rows`]
}
# "[[<-.idf" <- "[<-.idf" <- function(...) stop("Immutable")