使用某些数据标准时,使用多种方法查看data.frame的列会更简单。作为一个具体的例子,当使用SDTM数据进行临床试验时,每种数据类型(如实验室或生命体征)都有一个时间点列,名为" LBTPT"用于实验室和" VSTPT"用于生命体征。在加载数据时,理想情况下,我希望能够将该列引用为" LBTPT"或" TPT"。
具体来说,我想找到一种方法来完成以下工作:
d <- data.frame(LBTPT=1:3)
d <- alias_column(d, TPT="LBTPT")
d$TPT == d$LBTPT
但是,我希望数据只存储一次 - 它只是一个别名而不是副本。
而且,对于奖励积分,它可以在&#34;做我的意思&#34;与merge
,names<-
,bind_rows
等功能进行互动时的方式
答案 0 :(得分:6)
我的感觉是你可以通过使用R6和主动绑定来做到这一点。
考虑到这一点,我们可以创建一个例子。在这里,我们创建了两个iris数据集视图,我们使用两个不同的列名访问同一列。通过列名更改将更新共享的私有虹膜数据集。
我是R6的粉丝,因为它提供了一种维护(在这种情况下)数据帧引用语义的方法,同时允许多种方式来引用数据集。
NB。我希望这能指出你正确的方向。
interface ApiRequest<T> {
structuralTypingMatters: T;
}
require(R6)
data(iris)
dataframe_factory <- R6Class(
"dataframe_factory",
portable = FALSE,
lock_objects = FALSE,
private = list(
..iris_data = iris
),
active = list(
# add the binding here
Sepal.Length = function(x, ...) {
if ( missing(x) ) {
private$..iris_data$Sepal.Length
} else {
private$..iris_data$Sepal.Length[...] <<- x
}
},
another.Sepal.Length = function(x, ...) {
if ( missing(x) ) {
private$..iris_data$Sepal.Length
} else {
private$..iris_data$Sepal.Length[...] <<- x
}
}
)
)
# Create the DataFrame
my_Dataframe <- dataframe_factory$new()
# Retrieve the alias
my_Dataframe$Sepal.Length
my_Dataframe$another.Sepal.Length
my_Dataframe$Sepal.Length[1] <- 5
my_Dataframe$Sepal.Length[1]
my_Dataframe$another.Sepal.Length[2] <- 8
my_Dataframe$another.Sepal.Length[2]
my_Dataframe$Sepal.Length
my_Dataframe$another.Sepal.Length
head(my_Dataframe$Sepal.Length,2)
my_Dataframe$Sepal.Length[1:2]
identical(my_Dataframe$Sepal.Length, my_Dataframe$another.Sepal.Length)
identical(my_Dataframe$Sepal.Length[1], my_Dataframe$another.Sepal.Length[1])
identical(my_Dataframe$Sepal.Length[1:2], my_Dataframe$another.Sepal.Length[1:2])
答案 1 :(得分:3)
我会反驳自己的评论并给你一个可能有用的例子, 但有些人(包括我自己)会称之为可怕的黑客攻击&#34;:
setClass("aliased.data.frame", contains="data.frame")
make_alias <- function(original_name, alias) {
# make sure lazy evaluation doesn't bite us
force(original_name)
force(alias)
setMethod("$", signature(x="aliased.data.frame"), function(x, name) {
if (name == alias) name <- original_name
x[[name]]
})
}
在该示例中,我实际上是在隐藏$
方法以应用&#34;抗锯齿&#34;。
您必须类似地定义应支持别名的任何泛型。
举个例子,现在这可行:
> make_alias("a", "b")
> adf <- new("aliased.data.frame", data.frame(a=1:2))
> adf$b
[1] 1 2
> adf$a == adf$b
[1] TRUE TRUE
需要考虑一些棘手的方面。
例如,数据框的默认$
方法进行部分匹配:
> data.frame(aa=1:2)$a
[1] 1 2
答案 2 :(得分:1)
使用comment
和comment<-
函数/属性以及trace
函数,这是一个非常快速的技巧:
df <- head(iris)
comment(df) <- c(SL="Sepal.Length",SW="Sepal.Width")
trace(`$.data.frame`,quote(if(name %in% names(comment(df)))
name <- comment(df)[name]),print=FALSE)
df$SL
# [1] 5.1 4.9 4.7 4.6 5.0 5.4
identical(df$SL,df$Sepal.Length)
# [1] TRUE
默认情况下不打印评论属性,就像其他人看到的那样,调用:
comment(df)
取消trace
来电:
untrace(`$.data.frame`)
答案 3 :(得分:1)
@ Technophobe01提供的示例很好,但不太实用。 您总是为每个别名编写新函数和新类定义。很多工作!
来自Lisp,我在考虑你的问题。
在Lisp中,在这种情况下,可以定义用于查找别名的宏。
最酷的是reader-macros
。使用reader-macros,您可以改变方式,Lisp解释器如何看待&#34;代码。
大多数情况下,读者宏以#
开头。
但是,我们不在Lisp中。我们在R.我们没有这些可能性。
R中让R&#34;读取&#34;的唯一方法不同规则的表达是 -
要么重新定义$
方法(也许有一天我会提出这个解决方案 - 或者其他人...... - 但是一个很大的障碍是,$
是原始的 - 我们不幸运。 ..),或者然后:你使用一个函数(在我的情况下:with.alias()
缩写为:a()
用于alias
),其中规则被更改。这就是我去的方式。
使用我的解决方案,你可以这样做:
如何运作
# your normal data frame definition
df <- data.frame(LBTPT = 1:3)
# now df contains:
df
## LBTPT
## 1 1
## 2 2
## 3 3
# define your aliases for each data frame in this form:
define.alias(df, list("LBTPT" = "TPT"))
# within the `define.alias()` function, you give as the first argument
# the data frame symbol, for which aliases should be defined.
# the second argument is a list of "original name" = "alias" definitions.
# This is how you call your data frame with the alias name:
a(df$TPT) # returns what d$LBTPT returns
## actually `with.alias` but shortened to: `a`
# call within `a()` or `with.alias()` the data frame with the aliased column name.
# the function then looks up in the attributes `aliases` of the data frame
# the original name of the alias for the column and
# returns the value of the originally named column.
仅定义三个功能
这是您定义函数define.alias()
和with.alias()
以及简写a()
的方式:
define.alias <- function(df, alias.list) {
# revert definition list
l <- names(alias.list)
names(l) <- alias.list
l <- as.list(l)
# metaprogrammatically assign "aliases" attribute to data frame
df <- substitute(df)
alias.list <- substitute(l)
eval(bquote(attr(.(df), "aliases") <- .(alias.list)), env = parent.env(environment()))
}
.with.alias <- function(df.expr) {
exp <- df.expr
df <- exp[[2]]
l <- eval(bquote(attr(.(exp[[2]]), "aliases")), env = parent.env(environment()))
eval(bquote(substitute(.(exp), l)))
}
with.alias <- function(df.expr) {
exp <- substitute(df.expr)
l <- eval(bquote(attr(.(exp[[2]]), "aliases")), env = parent.env(environment()))
if (exp[[1]] == "<-") {
l <- eval(bquote(attr(.(exp[[2]][[2]]), "aliases")), env = parent.env(environment()))
eval(eval(bquote(substitute(.(.with.alias(exp[[2]])) <- .(exp[[3]])))), env = parent.env(environment()))
} else {
df <- exp[[2]]
eval(eval(bquote(substitute(.(exp), l))), env = parent.env(environment()))
}
} # that's it! works!
Tipp:你可以通过定义:
来节省一些打字# make `with.aliases` shorter:
a <- with.aliases
## and now:
a(df$TPT) # works, too!
好吧,但我必须继续使用'<-'
方法。
尽管
a
内的简单分配仍有效
a(df$TPT <- new.vetor) # assigns correctly
a(df$TPT[3] <- 3) # but this not yet ...
答案 4 :(得分:0)
如果您使用括号而不是美元符号来引用列,则可以使用:
d <- data.frame(LBTPT=1:3)
LBTPT = "LBTPT"
TPT = "LBTPT"
d[TPT] == d[LBTPT]
但是,我担心它确实能解决你所有的必需品。
答案 5 :(得分:0)
我最终使用了@Technophobe01和@Alexis的策略组合来生成以下解决方案:
library(methods)
setClass("dataframe_alias", representation=representation(data="data.frame", aliases="list"))
as.dataframe_alias <- function(x, aliases=list()) {
new("dataframe_alias", data=as.data.frame(x), aliases=aliases)
}
as.data.frame.dataframe_alias <- function(x, ...) {
x@data
}
`$.dataframe_alias` <- function(x, name) {
x[[name]]
}
`[[.dataframe_alias` <- function(x, name, ...) {
if (name %in% names(x@data)) {
x@data[[name, ...]]
} else if (name %in% names(x@aliases)) {
x@data[[x@aliases[[name]], ...]]
} else {
stop(name, " is not a name or alias for the dataframe_alias.")
}
}
names.dataframe_alias <- function(x) {
ret <- names(x@data)
attr(ret, "aliases") <- x@aliases
ret
}
alias_or_name_to_name <- function(object, alias) {
ret <- rep(NA_character_, length(alias))
mask_original_name <- alias %in% names(object@data)
mask_aliased_name <-
!mask_original_name &
alias %in% names(object@aliases)
mask_no_name <- !(mask_original_name | mask_aliased_name)
if (any(mask_no_name)) {
stop("Some aliases are not recognized as an original or aliased name: ",
paste(alias[mask_no_name], collapse=", "))
}
ret[mask_original_name] <- alias[mask_original_name]
ret[mask_aliased_name] <- unlist(object@aliases[alias])
ret
}
#' Add an alias to a dataframe_alias
#'
#' @param object A dataframe_alias object
#' @param ... named aliases to add in the form \code{alias=original_name}
#' @param rm Remove the alias(es)?
#' @return The updated \code{object}
#' @export
alias.dataframe_alias <- function(object, ..., rm=FALSE) {
args <- list(...)
if (is.null(names(args))) {
stop("Arguments must be named")
} else if (any(names(args) %in% "")) {
stop("All arguments must be named")
} else if (!all(unlist(args) %in% names(object))) {
# all arguments must map to actual data names (indirect alises are not
# currently permitted)
browser()
stop("All arguments must map to original data names")
}
for (nm in names(args)) {
object@aliases[[nm]] <- args[[nm]]
}
object
}
foo <- as.dataframe_alias(iris, aliases=list(foo="Sepal.Length"))
foo2 <- alias(foo, bar="Sepal.Length")
答案 6 :(得分:-1)
如果使用data.table,则内置别名。您可以
a <- data.table::as.data.table(survival::bladder)
originalNames <- data.table::copy(names(a)) # Create a copy of the original names
originalNames
b <- a # Allias the data.table. Not really needed.
data.table::setnames(b, old = 'event', new = "LBTPT")
dplyr::glimpse(b) # names in both data tables are changed
dplyr::glimpse(a) # names in both data tables are changed
data.table::setnames(a, old = names(b), new = originalNames) # Change back to old names when you are done
dplyr::glimpse(a) # Back to original