将函数应用于数据框中的分组行

时间:2017-09-25 22:37:21

标签: r dplyr purrr

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

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 个答案:

答案 0 :(得分:4)

由于您添加了dplyrpurrr的代码,因此我假设您对tidyverse解决方案感兴趣。下面我将演示基于tidyverse的解决方案。

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

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的一部分。< / p>

我们可以先根据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"表示我们要根据列表元素的名称创建名为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可以使用testdfleft_join合并,但我会在此处停止,因为testresult可能已经是您想要的所需输出。我希望这会有所帮助。

答案 1 :(得分:1)

从根本上说,主要问题涉及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 方法的输出返回是两列的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