【问题标题】:R/dplyr: Mutate based on multiple dynamic variable namesR/dplyr:基于多个动态变量名进行变异
【发布时间】:2020-08-13 20:52:54
【问题描述】:

我有一个数据框列表,每个数据框都包含多个变量,这些变量包含表面积值(以“_area”结尾)。对于每个表面积变量,我想使用相应的转换因子(以“_unit”结尾)来计算包含标准测量单位面积的第三个变量。我希望这些变量以“_area_ha”结尾。

以下是我的示例数据框:

a <- tibble(a1_area = c(1,1,1), a2_area_unit = c(1,1,0.5), a2_area = c(1,1,1),
            a1_area_unit = c(1,0.5,0.5), abc = c(1,2,3))

b <- tibble(b1_area = c(1,1,1), b1_area_unit = c(1,1,0.5), b2_area = c(1,1,1),
            b2_area_unit = c(1,0.5,0.5), abc = c(1,2,3))

ab_list <- list(a, b)

names(ab_list) <- c("a", "b")

我知道如何在循环的帮助下做到这一点,但想了解如何在 tidyverse/dplyr 逻辑中做到这一点。我的循环(它给了我想要的输出)看起来像这样:

df_names <- names(ab_list)

for (d in df_names) {

  df <- ab_list[[d]]
  var_names <- names(select(df, matches("_area$")))

    for (v in var_names) {
      int  <- df %>% select(all_of(v),)
      int2 <- df %>% select(matches(paste0(names(int), "_unit")))
      int3 <- int*int2
      names(int3) <- paste0(names(int), "_ha")
      df <- cbind(df, int3)
      rm(int, int2, int3)
      }

  ab_list[[d]] <- tibble(df)
  rm(df)
  }

> ab_list
$`a`
# A tibble: 3 x 7
  a1_area a2_area_unit a2_area a1_area_unit   abc a1_area_ha a2_area_ha
    <dbl>        <dbl>   <dbl>        <dbl> <dbl>      <dbl>      <dbl>
1       1          1         1          1       1        1          1  
2       1          1         1          0.5     2        0.5        1  
3       1          0.5       1          0.5     3        0.5        0.5

$b
# A tibble: 3 x 7
  b1_area b1_area_unit b2_area b2_area_unit   abc b1_area_ha b2_area_ha
    <dbl>        <dbl>   <dbl>        <dbl> <dbl>      <dbl>      <dbl>
1       1          1         1          1       1        1          1  
2       1          1         1          0.5     2        1          0.5
3       1          0.5       1          0.5     3        0.5        0.5

我尝试过使用 lapply 和 mutate_at,但我的方法不起作用。如果我理解正确,这是因为我的环境是嵌套的,我无法在计算变量“ha”的函数中访问 x。

ab_list %>% 
  lapply(function(x) mutate_at(x, vars(matches("_area$")), list(ha = ~.*x[[paste0(names(.),"_unit")]])))

Error: Column `a1_area_ha` must be length 3 (the number of rows) or one, not 0 

有没有办法让 mutate_at 中的函数根据函数中初始变量的名称访问父数据框中的变量?

如果有任何其他关于基于动态变量名称计算“_ha”变量的 tidyverse 方法的建议,我当然会很高兴。

【问题讨论】:

    标签: r tidyverse lapply dplyr


    【解决方案1】:

    很好的问题。下面是一个基本的 R 解决方案。我确信它可以适应 tidyverse 解决方案(例如,使用 purrr::map2())。在这里,我构建了一个进行基本测试的函数,然后将其与lapply() 一起使用。注意:答案是为您的示例量身定制的,因此如果值/单位的列名不同,则需要对其进行调整。希望这会有所帮助!

    val_by_unit <- function(data) {
    
      df <- data[order(names(data))]
    
      # Selecting columns for values and units
      val <- df[endsWith(names(df), "area")]
      unit <- df[endsWith(names(df), "unit")]
    
    
      # Check names are multiplying correctly
      if(!all(names(val) == sub("_unit", "", names(unit)))) {
        stop("Not all areas have a corresponding unit")
      }
    
      # Multiplying corresponding columns
      output <- Map(`*`, val, unit)
    
      # Renaming output and adding columns   
      data[paste0(names(output), "_ha")] <- output
      data
    }
    

    结果

    lapply(ab_list, val_by_unit)
    
    $a
    # A tibble: 3 x 7
      a1_area a2_area_unit a2_area a1_area_unit   abc a1_area_ha a2_area_ha
        <dbl>        <dbl>   <dbl>        <dbl> <dbl>      <dbl>      <dbl>
    1       1          1         1          1       1        1          1  
    2       1          1         1          0.5     2        0.5        1  
    3       1          0.5       1          0.5     3        0.5        0.5
    
    $b
    # A tibble: 3 x 7
      b1_area b1_area_unit b2_area b2_area_unit   abc b1_area_ha b2_area_ha
        <dbl>        <dbl>   <dbl>        <dbl> <dbl>      <dbl>      <dbl>
    1       1          1         1          1       1        1          1  
    2       1          1         1          0.5     2        1          0.5
    3       1          0.5       1          0.5     3        0.5        0.5
    

    【讨论】:

    • 谢谢!这对我来说很好,并且避免了循环。我也会研究map2。由于我的实际数据框更长,并且我有多个值/单位组合(面积/公顷、重量/千克等),我已经修改了您的函数以也接受值的参数,例如“面积”,单位,例如“单位”和输出单位,例如“哈”。
    【解决方案2】:

    tidyverse 函数最适用于“长”格式的数据,其中每一行都代表一个唯一的数据点。为此,您需要使用tidyr::pivot_longer 函数:

    # Join dataframes
    dplyr::bind_cols(a, b) %>%
    # Convert to area columns to long format
    tidyr::pivot_longer(
      cols = dplyr::ends_with('area'),
      names_to = 'site',
      values_to = 'area'
    ) %>%
    # Convert unit columns to long format
    tidyr::pivot_longer(
      cols = dplyr::ends_with('unit'),
      names_to = 'site2',
      values_to = 'unit'
    ) %>%
    # Just extract first 2 characters of the site column to get unique ID
    dplyr::mutate(
      site = stringr::str_sub(site, 1, 2)
    ) %>%
    # Remove redundant columns
    dplyr::select(abc, site, area, unit) %>%
    # Calculate area in HA
    dplyr::mutate(
      area_ha = area * unit
    )
    

    一旦您的数据为长格式,您只需使用dplyr::mutate 将您的面积列乘以单位列即可得到 area_ha 列。如果要将数据转换回其原始格式,可以使用tidyr::pivot_wider 将数据转换回宽格式,这将为您提供名称为 a1_area_ha、a2_area_ha 等的列。

    【讨论】:

    • 这个解决方案对我来说很好,直到我尝试使用 pivot_wider 转换回原始格式:tidyr::pivot_wider(names_from = site, values_from = c(area, area_ha, unit.使用它会为 values_from 中的每个元素提供错误消息(1:area 中的值不是唯一标识的;输出将包含列表列等)。我尝试在 pivot_wider 之前使用唯一标识符(mutate(row = row_number ())) 但这只会导致更大的数据框 (12 x 8),每行包含 a1 或 a2 的值,而其余的是 NA 值。你知道如何解决这个问题吗?
    猜你喜欢
    • 2018-09-29
    • 1970-01-01
    • 1970-01-01
    • 2020-07-08
    • 1970-01-01
    • 2022-01-20
    • 2018-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多