【问题标题】:For loop: Count matches and unique elements among two dataframes and apply function to countsFor循环:计算两个数据帧之间的匹配和唯一元素并将函数应用于计数
【发布时间】:2017-12-08 18:08:39
【问题描述】:

我想进行一个非常复杂的循环。我有多个区域,每个区域在我的真实数据框中都有数百个图。我想按区域划分子集,然后在子集上绘制和执行各种函数,以最终计算出仅归因于共享物种的差异。我将首先说每一行都代表一个交互。

我的例子df

 set.seed(540)
 df<- data.frame(region= c(rep(1, 16), rep(2,8)), 
            plot= c(rep("A",5), rep("B",9), rep("C", 2), rep("D", 6),rep("E", 2)), 
            plantsp= sample(1:24,24, replace= TRUE), 
            lepsp= sample(1:24,24,replace= TRUE), 
            psitsp= sample(1:24,24,replace= TRUE))
 df[] <- lapply(df, as.character)
 df$plantsp<-paste('plantsp', df$plantsp, sep='_')
 df$lepsp<-paste('lepsp', df$lepsp, sep='_')
 df$psitsp<-paste('psitsp', df$psitsp, sep='_')
 df$paste1<- paste(df$plantsp, df$lepsp, sep='_')
 df$paste2<- paste(df$lepsp, df$psitsp, sep='_')
 df$paste3<- paste(df$plantsp,df$lepsp, df$psitsp)

步骤 1:按区域对 df 进行子集。示例:

region_sub <- split(df, df$region)

步骤 2:按图对 df 进行子集。示例:

plot_sub <- split(region_sub[[1]], region_sub[[1]][[2]])

Step3:我们将上述步骤中的每个子集(每个列表组件)称为绘图子集。在此示例中,我将使用第一个子集 (region1, plotA) 作为所有后续输出的示例。我将调用这个 region1,plotA 子集plot_sub1。我想将plot_sub1 与原始df 进行比较,以生成三个df 子集。我们将它们称为df_sub1df_sub2df_sub3。首先,df_sub1 包含plantsplepsp 列中plot_sub1df 中的条目之间的匹配。删除具有任何唯一条目的行,以及匹配 plantsp 但不匹配 lepsp 的行,反之亦然。 df_sub1 示例:

df_sub1<- df[c(1,2,3,4,5,22),c(1:4,6)] 

请注意,仅保留那些具有共享物种的行。此外,只有那些具有共享物种并且也相互作用的行仍然存在。另外,我删除了不必要的列(例如psitsppaste2paste3),以提醒您注意此步骤的结果。不需要为代码删除这些列。

第 4 步:对 lepsppsitsp 列重复第 3 步以生成 df_sub2。示例:

df_sub2<- df[1:5,c(1:2,4,5,7)] 

步骤 5:对 plantsplepsppsitsp 列重复步骤 3,以生成 df_sub3。示例:

df_sub3<- df[1:5,c(1:5,8)] 

Step6: 现在所有子集都制作好了,我想计算plot_sub1df_sub1 (=5) 中paste1 列中的匹配元素。例子: 这将存储在向量match 中。结果将相应地存储在匹配或唯一向量中。示例:

match<- length(intersect(df_sub1$paste1,  plot_sub[[1]]$paste1))
match

我还想计算唯一元素 (=1)。这将存储在向量unique 中。这将对plot_sub1df_sub2plot_sub1df_sub3 重复。我不确定如何计算两个 df 中的唯一元素,因此我无法提供示例代码。

 unique<- 1

注意:df_sub 重复交互或匹配时,plot_sub 之间的匹配只需要计算 1 次。这需要考虑匹配的存在 - 不存在,而不是丰富。

对于这个子集,这两个向量将是:

match<- c( length(intersect(df_sub1$paste1,  plot_sub[[1]]$paste1)),  
length(intersect(df_sub2$paste2,  plot_sub[[1]]$paste2)),  
length(intersect(df_sub3$paste3,  plot_sub[[1]]$paste3))

match

unique<-c(1,0,0)

然后将对每个向量求和。示例:

sum_match<- 15
sum_unique<- 1

Step7:最后,将这些值输入到函数中: ((a + b)/((2*a + b)/2) - 1) 其中 a= sum_match 和 b=sum_unique。 然后将该值输入到结果向量res_vec

步骤 8:这个过程(步骤 3-7)将针对每个绘图子集进行迭代。

实际上,这将计算情节交互和相应元网络(所有可能的交互)之间共享交互的差异。这是对 (Poisot et al 2012) 的修改,以解释三养相互作用。

这很可悲,但要启动 for 循环我有:

res_vec<- NA

for (i in 1:length(unique(df$region)))
  {
      for (j in 1:length(unique(df$plot)))
     {

我真的很感激有人愿意帮助我实现循环中的论点。这就是让我感到棘手的地方。

【问题讨论】:

  • 我很欣赏这个可重现的例子,但我对df_sub1 的逻辑感到困惑。在您的示例中,您说df_sub1 应该包括df 的第22 行,其中包含plantsp_9 和“lepsp_2”。但是,plot_sub1 中的任何行都没有这两个值。因此,我不明白为什么df[22, ] 包含在df_sub1 中。此外,df[16, ]plantsp_21lepsp_19,与plot_sub1[2, ] 完全匹配,所以我也不明白为什么df[16, ] 不包含在df_sub1 中。
  • 也许它与“删除任何唯一条目的行”有关,在这种情况下我也不明白,因为 df[c(1,2,3,4,5,22),c(1:4,6)] 的所有行都是唯一的,并且在 @987654386 内@列只有plantsp_9重复,其他四个值在df_sub1内是唯一的
  • 感谢您提出的问题,而不是放弃我的问题!你的问题很好,为什么我的分析(在我看来)很酷。要回答您的第一个问题:在df_sub1 的第 22 行,而植物 p9 和 lepsp2 在plot_sub1 中不交互,它们都在该图中找到。所以我想知道,在它们共享的物种子集中,有什么不同的相互作用。通过包含 row22,我正在考虑以不同方式相互作用的共享物种,我需要计算这些实例。
  • 在我的示例中我不考虑df[16,],因为这是情节 C 的一部分,它是循环的第三次迭代的一部分。我需要为每个绘图子集分别重复步骤 3-7(例如,在循环的下一次迭代中将是 plot_sub2 及其对应的 df_sub1df_sub2df_sub3,第三次迭代是 plot_sub3与其对应的df_sub1df_sub2df_sub3等。
  • 好的,我想我对 df 第 22 行的了解更好 - 它包含在 df_sub1 中,因为它的 plantsplepsp 值都出现在 @987654403 的 somewhere 中@,即使它们不在同一行中。对吗?

标签: r for-loop


【解决方案1】:

感谢 @Gregor 为您在 cmets 中所做的所有澄清!

这是我使用tidyverse 的解决方案。

代码+解释

## Load packages
library(tidyverse)

## Nest data
new_df <- df %>% 
  group_by(region, plot) %>% 
  nest(.key = plot_sub) 

new_df

# A tibble: 5 x 3
#     region   plot         plot_sub
#      <dbl> <fctr>           <list>
#   1      1      A <tibble [5 x 3]>
#   2      1      B <tibble [9 x 3]>
#   3      1      C <tibble [2 x 3]>
#   4      2      D <tibble [6 x 3]>
#   5      2      E <tibble [2 x 3]>

plot_sub 列包含与您问题中的同名列表相同的数据。将此列视为数据框列表。

我知道写一个函数来创建df_sub's。这使我们的代码更加干净,并避免了不必要的重复。然后这个函数将应用到我们的专栏plot_sub

# Function to create the df_sub
# Takes the plot_sub, original dataframe (df) and a list of columns, which should be compared
# Returns the desired df_sub with new interactions of species which are in plot_sub
# Only unique interactions are returned

create_df_sub <-  function(plot_sub, df, col_list){
  # Filter df such that it only contains species which are in plot_sub
  for (x in col_list) {
    df <- df[df[[x]] %in% plot_sub[[x]], ]
  }

  # Combine plot_sub and filtered df
  df_sub <- rbind(plot_sub[, col_list], df[, col_list]) 
  # Paste relevant colums together
  df_sub$paste_col <- do.call(paste, c(df_sub[, col_list], sep = '_'))
  # Exclude duplicated values
  df_sub <- df_sub[!duplicated(df_sub$paste_col), ]

  return(df_sub)
}

现在我定义要创建 df_sub 的列,然后将函数应用到 plot_sub-列

col_list1 <- c('plantsp', 'lepsp')
col_list2 <- c('lepsp', 'psitsp')
col_list3 <- c('plantsp', 'lepsp', 'psitsp')

new_df <- new_df %>% 
  mutate(df_sub1 = map(plot_sub, create_df_sub, df = df, col_list = col_list1), 
         df_sub2 = map(plot_sub, create_df_sub, df = df, col_list = col_list2), 
         df_sub3 = map(plot_sub, create_df_sub, df = df, col_list = col_list3)) 

map 将向量或列表作为参数,并将指定的函数应用于每个元素(如lapply)。比较df_sub1plot_sub 的第一个元素以查看差异。

new_df$plot_sub[[1]]
# A tibble: 5 x 3
#      plantsp    lepsp    psitsp
#        <chr>    <chr>     <chr>
# 1  plantsp_2 lepsp_19 psitsp_19
# 2 plantsp_21 lepsp_19  psitsp_4
# 3 plantsp_19  lepsp_2 psitsp_11
# 4  plantsp_9 lepsp_13 psitsp_24
# 5 plantsp_24  lepsp_9  psitsp_2

new_df$df_sub1[[1]]
# A tibble: 6 x 3
#      plantsp    lepsp           paste_col
#        <chr>    <chr>               <chr>
# 1  plantsp_2 lepsp_19  plantsp_2_lepsp_19
# 2 plantsp_21 lepsp_19 plantsp_21_lepsp_19
# 3 plantsp_19  lepsp_2  plantsp_19_lepsp_2
# 4  plantsp_9 lepsp_13  plantsp_9_lepsp_13
# 5 plantsp_24  lepsp_9  plantsp_24_lepsp_9
# 6  plantsp_9  lepsp_2   plantsp_9_lepsp_2

df_sub1中添加了新的交互。

为了提取匹配值和唯一值,我在plot_sub 列上使用inner_joinanti_join 以及不同的df_sub

new_df <- new_df %>%
  mutate(match1 = map2(df_sub1, plot_sub, inner_join, by = col_list1), 
         match2 = map2(df_sub2, plot_sub, inner_join, by = col_list2), 
         match3 = map2(df_sub3, plot_sub, inner_join, by = col_list3), 
         unique1 = map2(df_sub1, plot_sub, anti_join, by = col_list1), 
         unique2 = map2(df_sub2, plot_sub, anti_join, by = col_list2), 
         unique3 = map2(df_sub3, plot_sub, anti_join, by = col_list3)) 

inner_join 返回在by-参数中指定的列中具有匹配值的所有行,而anti_join 返回df_sub 中不匹配的所有行。 这里我使用map2-函数,它接受两个向量/列表并应用指定的函数。

new_df$match1[[1]]
# A tibble: 5 x 4
#      plantsp    lepsp    psitsp           paste_col
#        <chr>    <chr>     <chr>               <chr>
# 1  plantsp_2 lepsp_19 psitsp_19  plantsp_2_lepsp_19
# 2 plantsp_21 lepsp_19  psitsp_4 plantsp_21_lepsp_19
# 3 plantsp_19  lepsp_2 psitsp_11  plantsp_19_lepsp_2
# 4  plantsp_9 lepsp_13 psitsp_24  plantsp_9_lepsp_13
# 5 plantsp_24  lepsp_9  psitsp_2  plantsp_24_lepsp_9

new_df$unique1[[1]]
# A tibble: 1 x 3
#     plantsp   lepsp         paste_col
#       <chr>   <chr>             <chr>
# 1 plantsp_9 lepsp_2 plantsp_9_lepsp_2

在最后一步中,我提取每个matchunique 的行数并总结起来。我还计算了res_vec

new_df <- new_df %>%
  mutate(sum_match = map_int(match1, nrow) + map_int(match2, nrow) + map_int(match3, nrow), 
         sum_unique = map_int(unique1, nrow) + map_int(unique2, nrow) + map_int(unique3, nrow), 
         res_vec = ((sum_match + sum_unique)/((2*sum_match + sum_unique)/2)) - 1)

这里我使用map_int,因为我的返回值是一个整数,我想直接用它来求和。仅使用 map 会返回一个列表,我首先必须将其转换为整数向量。

new_df %>% select(region, plot, sum_match, sum_unique, res_vec)
# A tibble: 5 x 5
#   region   plot sum_match sum_unique    res_vec
#    <dbl> <fctr>     <int>      <int>      <dbl>
# 1      1      A        15          1 0.03225806
# 2      1      B        27          3 0.05263158
# 3      1      C         6          2 0.14285714
# 4      2      D        18          1 0.02702703
# 5      2      E         6          0 0.00000000

数据

set.seed(540)
df <- data.frame(region = c(rep(1, 16), rep(2, 8)), 
                plot = c(rep('A', 5), rep('B', 9), rep('C', 2), rep('D', 6),rep('E', 2)), 
                plantsp = sample(1:24, 24, replace = TRUE), 
                lepsp = sample(1:24, 24, replace = TRUE), 
                psitsp = sample(1:24, 24, replace = TRUE))
df$plantsp <- paste('plantsp', df$plantsp, sep = '_')
df$lepsp <- paste('lepsp', df$lepsp, sep = '_')
df$psitsp <- paste('psitsp', df$psitsp, sep = '_')

【讨论】:

  • 哇。我很感激也很感动。这很好用。唯一的小修复是res_vec 公式。它没有正确计算答案。 res_vec 应该是 res_vec &lt;- c(0.032, 0.052, 0.143, 0.027, 0)。那是我的错误,我将在我的帖子中进行编辑。如果您将答案更新为res_vec = ((sum_match + sum_unique)/((2*sum_match + sum_unique)/2)) - 1) ,这将是正确答案。
  • 我没有在我的真实数据上运行这个。知道这种方法或循环是否会更快吗?这将是一个庞大的数据集(即超过 9 个区域和 1500 个图),我很好奇您的解决方案与循环相比运行速度有多快。
  • 我猜这比循环更快,但您可以使用microbenchmark 首先比较不同的方法,然后选择最快的方法。
  • 这是一个很好的解决方案,感谢microbenchmark 的建议,我不知道这将有很大帮助。我已经更新了我的 OP 来解释我的错误,所以我建议你更新你的答案。感谢您为此付出的所有时间。
  • 非常好!我为还没有一起得到答案而感到难过——很高兴你得到了答案!
猜你喜欢
  • 2016-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多