首页 文章

将函数应用于数据框中的分组行[重复]

提问于
浏览
4

这个问题在这里已有答案:

我创建了一个计算大量生物统计数据的函数,例如物种范围边缘 . 这是该函数的简化版本:

range_stats <- function(rangedf, lat, lon, weighting, na.rm=T){
  cent_lat <- weighted.mean(x=rangedf[,lat], w=rangedf[,weighting], na.rm=T)
  cent_lon <- weighted.mean(x=rangedf[,lon], w=rangedf[,weighting], na.rm=T)
out <- data.frame(cent_lat, cent_lon)    
return(out)
}

我想将它应用于一个大型数据框,其中每一行都是一个物种的观察 . 因此,我希望函数按指定的一组列对行进行分组,然后为每个组计算这些统计信息 . 这是一个测试数据帧:

LATITUDE <- c(27.91977, 21.29066, 26.06340, 28.38918, 25.97517, 27.96313)
LONGITUDE <- c(-175.8617, -157.8645, -173.9593, -178.3571, -173.9679, -175.7837)
BIOMASS <- c(4.3540488, 0.2406332, 0.2406332, 2.1419699, 0.3451426, 1.0946017)
SPECIES <- c('Abudefduf abdominalis','Abudefduf abdominalis','Abudefduf abdominalis','Chaetodon lunulatus','Chaetodon lunulatus','Chaetodon lunulatus')
YEAR <- c('2005', '2005', '2014', '2009', '2009', '2015')
testdf <- data.table(LATITUDE, LONGITUDE, BIOMASS, SPECIES, YEAR)

我想将此函数应用于物种和年份的每个独特组合,以计算汇总统计数据,即,以下内容:

testresult <- testdf %>%
  group_by(SPECIES, YEAR) %>%
  range_stats(lat="LATITUDE",lon="LONGITUDE",weighting="BIOMASS",na.rm=T)

但是,上面的代码不起作用(我得到 (list) object cannot be coerced to type 'double' 错误),我不知道如何解决问题 .

2 回答

  • 1

    由于您添加了 dplyrpurrr 的标记,我假设您对 tidyverse 解决方案感兴趣 . 所以下面我将演示一个基于 tidyverse 的解决方案 .

    首先,你的 range_stats 是有问题的 . 这就是您收到错误消息的原因 . weighted.mean 期待 xw 参数的向量 . 但是,如果 rangedftibble ,则 tibble 的子集方式(例如 rangedf[,lat] )仍将返回单列 tibble . 更好的方法是使用 dplyr 包中的 pull .

    library(tidyverse)
    range_stats <- function(rangedf, lat, lon, weighting, na.rm=T){
      cent_lat <- weighted.mean(x = rangedf %>% pull(lat), 
                                w = rangedf %>% pull(weighting), na.rm=T)
      cent_lon <- weighted.mean(x = rangedf %>% pull(lon), 
                                w = rangedf %>% pull(weighting), na.rm=T)
      out <- data.frame(cent_lat, cent_lon)    
      return(out)
    }
    

    接下来,您创建数据框的方式是正常的,但 data.table 来自 data.table 包,您将创建 data.table ,而不是 tibble . 我以为你想使用 tidyverse 的方法,所以我将 data.table 更改为 data_frame ,如下所示 .

    LATITUDE <- c(27.91977, 21.29066, 26.06340, 28.38918, 25.97517, 27.96313)
    LONGITUDE <- c(-175.8617, -157.8645, -173.9593, -178.3571, -173.9679, -175.7837)
    BIOMASS <- c(4.3540488, 0.2406332, 0.2406332, 2.1419699, 0.3451426, 1.0946017)
    SPECIES <- c('Abudefduf abdominalis','Abudefduf abdominalis','Abudefduf abdominalis','Chaetodon lunulatus','Chaetodon lunulatus','Chaetodon lunulatus')
    YEAR <- c('2005', '2005', '2014', '2009', '2009', '2015')
    testdf <- data_frame(LATITUDE, LONGITUDE, BIOMASS, SPECIES, YEAR)
    

    现在,您说要将 range_stats 函数应用于 SPECIESYEAR 的每个组合 . 一种方法是将数据帧拆分为数据帧列表,并使用 lapply 族函数 . 但是在这里我想向您展示如何使用 map 族函数来实现此任务,因为 map 来自 purrr 包,它是 tidyverse 的一部分 .

    我们可以首先根据 SPECIESYEAR 创建组索引 .

    testdf2 <- testdf %>%
      mutate(Group = group_indices(., SPECIES, YEAR)) 
    testdf2
    # A tibble: 6 x 6
      LATITUDE LONGITUDE   BIOMASS               SPECIES  YEAR Group
         <dbl>     <dbl>     <dbl>                 <chr> <chr> <int>
    1 27.91977 -175.8617 4.3540488 Abudefduf abdominalis  2005     1
    2 21.29066 -157.8645 0.2406332 Abudefduf abdominalis  2005     1
    3 26.06340 -173.9593 0.2406332 Abudefduf abdominalis  2014     2
    4 28.38918 -178.3571 2.1419699   Chaetodon lunulatus  2009     3
    5 25.97517 -173.9679 0.3451426   Chaetodon lunulatus  2009     3
    6 27.96313 -175.7837 1.0946017   Chaetodon lunulatus  2015     4
    

    如您所见, Group 是显示索引号的新列 . 现在我们可以基于 Group 拆分数据框,然后使用 map_dfr 来应用 range_stats 函数 .

    testresult <- testdf2 %>%
      split(.$Group) %>%
      map_dfr(range_stats, lat = "LATITUDE",lon = "LONGITUDE", 
              weighting = "BIOMASS", na.rm = TRUE, .id = "Group")
    testresult
      Group cent_lat  cent_lon
    1     1 27.57259 -174.9191
    2     2 26.06340 -173.9593
    3     3 28.05418 -177.7480
    4     4 27.96313 -175.7837
    

    请注意, map_dfr 可以自动将数据帧的输出列表绑定到单个数据帧 . .id = "Group" 表示我们要根据list元素的名称创建一个名为 Group 的列 .

    我将这个过程分为两个步骤,但当然它们可以在一个管道中完成,如下所示 .

    testresult  <- testdf %>%
      mutate(Group = group_indices(., SPECIES, YEAR))  %>%
      split(.$Group) %>%
      map_dfr(range_stats, lat = "LATITUDE",lon = "LONGITUDE", 
              weighting = "BIOMASS", na.rm = TRUE, .id = "Group")
    

    如果你愿意, testresult 可以使用 left_jointestdf 合并,但我会在此停止,因为 testresult 可能已经是您想要的所需输出 . 我希望这有帮助 .

  • 3

    从根本上说,主要问题涉及 weighted.mean() ,您传递的是数据框对象,而不是可以强制转换为double的矢量 . 要修复方法,只需更改:

    x=rangedf[,lat]
    

    双括号:

    x=rangedf[[lat]]
    

    调整方法:

    range_stats <- function(rangedf, lat, lon, weighting, na.rm=T){
      cent_lat <- weighted.mean(x=rangedf[[lat]], w=rangedf[[weighting]], na.rm=T)
      cent_lon <- weighted.mean(x=rangedf[[lon]], w=rangedf[[weighting]], na.rm=T)
      out <- data.frame(cent_lat, cent_lon)    
      return(out)
    }
    

    至于整体分组计算,请原谅我绕过你使用的 dplyrdata.table ,并考虑基础R未充分利用但有用的方法, by() .

    当前设置的挑战是range_stats方法的输出return是两列的data.frame,而dplyr的 group_by() 期望一个聚合向量操作 . 但是, by 将数据框对象(按因子切片)传递到已定义的函数中,以返回data.frames列表,然后您可以 rbind 获取最终的一个数据帧:

    df_List <- by(testdf, testdf[, c("SPECIES", "YEAR")], FUN=function(df)
                    data.frame(species=df$SPECIES[1],
                               year=df$YEAR[1],
                               range_stats(df,"LATITUDE","LONGITUDE","BIOMASS"))
                  )
    
    finaldf <- do.call(rbind, df_List)
    finaldf
    #                 species year cent_lat  cent_lon
    # 1 Abudefduf abdominalis 2005 27.57259 -174.9191
    # 2   Chaetodon lunulatus 2009 28.05418 -177.7480
    # 3 Abudefduf abdominalis 2014 26.06340 -173.9593
    # 4   Chaetodon lunulatus 2015 27.96313 -175.7837
    

相关问题