如何选择函数并将其应用于数据框列表中的多列

时间:2014-03-27 21:38:31

标签: r

我正在使用两个共享几个相似列的数据帧列表,我希望能够使用列名而不是列位置一次性转换每个数据帧中几列的类。

我搜索了StockOverflow并在此处找到了类似的问题:此处:Using lists to change columns in multiple dataframes in R和此处:applying a function for a list of dataframes。但是,我试图使用多个列名来转换日期。 以下是一个示例数据来说明我的问题:

df1 <- data.frame("t1" = c(20070103, 20070104, 20070105, 20070108, 20070109), "t2" = c(20070110,20070111, 20070112, 20070113, 20070114), A = 1:5)
df2 <- data.frame("t1" = c(20080103, 20080104, 20080105, 20080108, 20080109), "t2" = c(20080110,20080111, 20080112, 20080113, 20080114), B = 1:5)
l <- list(df1 = df1, df2=df2)

到目前为止,我找到了两个解决方案,我可以为每个要转换为日期的列重复这些解决方案:

#1
l2 <-lapply(l, function(x) transform(x, t1 = as.Date(as.character(t1), "%Y%m%d")))

#2
f <- function(df){
    within(df, t1 <- as.date(date))
}
l2 <- lapply(l, f)

但是,我是否可以使用任一方法一次性获取多个列(而不是整个数据帧或列表)并使用列名?我尝试过以下代码无济于事:

periods <- c( "t1", "t2" )
ls2 <-lapply(ls, function(x) transform(x, periods = as.Date(as.character(periods), "%Y%m%d")) 

f <- function(df) {
     within(df, t1 <- as.Date(as.character(t1), "%Y%m%d"))
     within(df, t2 <- as.Date(as.character(t2), "%Y%m%d"))
         }
l2 <- lapply(l, f)

for (i in periods)
    l2 <-lapply(l, function(x) transform(x, i = as.Date(as.character(i), "%Y%m%d")))

2 个答案:

答案 0 :(得分:3)

  • 建议#1,简单:

    lapply(l, function(dfrm, periods, fmt) {
        for (ff in which(colnames(dfrm) %in% periods))
            dfrm[,ff] <- as.Date(as.character(dfrm[,ff]), fmt)
        dfrm
    }, periods=c('t1', 't2'), fmt='%Y%m%d')
    

    使用ff in which(...)允许我们指定列标题 可能包括也可能不包括在内,如果部分或全部未做任何更改 在特定的data.frame中空缺。

    lapply的第二和第三个论点,periods=c('t1','t2'), 允许您指定格式和列名称(干净地) 将它们带入内环(没有内部环路) 循环到达数据的外部,如果/何时会咬你 您将代码复制/粘贴到另一个项目中。)

  • 建议#2,尝试转换所有列:

    lapply(l, function(dfrm, fmt) {
        for (cc in seq.int(ncol(dfrm)))
            if (! is.na(as.Date(as.character(dfrm[1,cc]), format=fmt)))
                dfrm[,cc] <- as.Date(as.character(dfrm[,cc]), format=fmt)
        dfrm
    }, fmt='%Y%m%d')
    

    如果您有其他可以 推断的列,则可能会失败 作为日期(使用这些启发式方法),但并非如此。

    我将检查限制在第一行以获得性能,如果大的话 大量的数据会导致这成为性能瓶颈。

  • 建议#3,同样的事情,但对错误警报更加健壮:

    lapply(l, function(dfrm, fmt) {
        for (cc in seq.int(ncol(dfrm))) {
            tmp <- as.Date(as.character(dfrm[,cc]), format=fmt)
            if (! any(is.na(tmp))) dfrm[,cc] <- tmp
        }
        dfrm
    }, fmt='%Y%m%d')
    

    好的,我们通过检查减少了误报的数量 将某些所有值转换为日期,但这意味着 如果任何一个单元格在其他有效的日期列中失败,那么 整栏受苦。你也许可以绕过这个 检查失败的百分比数,但现在我们得到了 有点荒谬......

  • 建议#4,在列名上使用正则表达式:

    lapply(l, function(dfrm, regex, fmt) {
        for (cc in grep(regex, colnames(dfrm)))
            dfrm[,cc] <- as.Date(as.character(dfrm[,cc]), format=fmt)
        dfrm
    }, regex='^t[0-9]+$', fmt='%Y%m%d')
    

    如果你不舒服,这可能会引发其他问题 正则表达式。

这些可以使用嵌套*apply代替for来完成 循环,但由于R现在表现得非常好,像这样的循环,我 不要认为这是一个大问题。 (这取决于你的大小 数据。)

如果您对列的命名约定更加满意 标题,然后#4可能是你的答案。如果没有(或者你不是 舒适的正则表达式)但你有信心 非日期列不会被误解,那么#2或#3也能正常工作。

答案 1 :(得分:1)

l.new <- lapply(l, function(x) {x[periods] <- lapply(x[periods], as.character); x})
str(l.new)

产生

List of 2
 $ df1:'data.frame':    5 obs. of  3 variables:
  ..$ t1: chr [1:5] "20070103" "20070104" "20070105" "20070108" ...
  ..$ t2: chr [1:5] "20070110" "20070111" "20070112" "20070113" ...
  ..$ A : int [1:5] 1 2 3 4 5
 $ df2:'data.frame':    5 obs. of  3 variables:
  ..$ t1: chr [1:5] "20080103" "20080104" "20080105" "20080108" ...
  ..$ t2: chr [1:5] "20080110" "20080111" "20080112" "20080113" ...
  ..$ B : int [1:5] 1 2 3 4 5

更新:为了获取日期,您可以使用:

lapply(
  l, 
  function(x) {
    x[periods] <- lapply(x[periods], function(x) as.Date(as.character(x), format="%Y%m%d")); 
    x
} )