是否可以在R

时间:2018-05-19 14:59:34

标签: r dataframe

使用某些数据标准时,使用多种方法查看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;与mergenames<-bind_rows等功能进行互动时的方式

7 个答案:

答案 0 :(得分:6)

我的感觉是你可以通过使用R6和主动绑定来做到这一点。

  • R6方法属于对象,而不是泛型。
  • R6对象是可变的:通常的copy-on-modify语义不适用。

考虑到这一点,我们可以创建一个例子。在这里,我们创建了两个iris数据集视图,我们使用两个不同的列名访问同一列。通过列名更改将更新共享的私有虹膜数据集。

我是R6的粉丝,因为它提供了一种维护(在这种情况下)数据帧引用语义的方法,同时允许多种方式来引用数据集。

NB。我希望这能指出你正确的方向。

R6示例(这里我们创建了两个虹膜数据集视图):

interface ApiRequest<T> {
    structuralTypingMatters: T;
}

R6控制台控制台输出:

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)

使用commentcomment<-函数/属性以及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,则内置别名。您可以

  • 使用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