【问题标题】:R: how to add rows based on the value in a columnR:如何根据列中的值添加行
【发布时间】:2018-03-15 18:53:58
【问题描述】:

我有一个如下所示的数据框:

line = c(1, 2, NA, 4 ,5, NA, 7)
group = c("1.0 Group A", "2.0 Group B", "3.0 Group C", "4.0 Group D", "5.0  Group E", "6.0 Group F", "7.0 Group G")
df <- data.frame(line, group)

view(df)
   line    group
1    1   1.0 Group A
2    2   2.0 Group B
3   NA   3.0 Group C
4    4   4.0 Group D
5    5   5.0 Group E
6   NA   6.0 Group F
7    7   7.0 Group G

我想要做的是在“行”列中找到所有 NA 值,并在“组”列中的该行下方放置一行,表示“不适用”。这样新的数据框应该如下所示:

view(df)
    line    group
1    1   1.0 Group A
2    2   2.0 Group B
3   NA   3.0 Group C
4   NA   Not Applicable
5    4   4.0 Group D
6    5   5.0 Group E
7   NA   6.0 Group F
8   NA  Not Applicable
9    7   7.0 Group G

我正在考虑使用 ifelse 语句或使用 dplyr 中的 case_when。但我不知道如何解决。有人有什么建议吗?

谢谢!

【问题讨论】:

  • 这样做有什么特别的原因吗?这不是一个非常 tidy 的方法,因为您基本上是在表中插入实际上不是值的值。如果你想在这些特定的边界上做点什么,你可以创建一个索引向量;如果你想为一些可视化划分组,你可以创建一个组 id 变量。
  • 谢谢@CalumYou!是的,我正在处理更大的数据,实际上需要 R 自动识别 NA 并在其下方放置一行“不适用”,而不是我手动执行。我会尝试研究索引向量。
  • 这里是related question 以获取更多信息,但您的问题已概括为插入多行。而且我认为 2015 年 12 月的 dplyr 解决方案不尊重插入顺序?

标签: r


【解决方案1】:

这是一个基本的 R 方法:通过累积的 NA 计数拆分数据,添加新行,重新组合。

    df$group = as.character(df$group)
    split_df = split(df, cumsum(is.na(df$line)))
    split_df[-1] = lapply(split_df[-1], function(d) rbind(d[1, ], data.frame(line = NA, group = "Not applicable"), d[-1, ]))
    do.call(rbind, split_df)
    #     line          group
    # 0.1    1    1.0 Group A
    # 0.2    2    2.0 Group B
    # 1.3   NA    3.0 Group C
    # 1.1   NA Not applicable
    # 1.4    4    4.0 Group D
    # 1.5    5   5.0  Group E
    # 2.6   NA    6.0 Group F
    # 2.1   NA Not applicable
    # 2.7    7    7.0 Group G

请注意,我将group 转换为character 以方便添加新值,并将NAs 放在line 列中 - 数字向量中不能只包含空格,每个元素都需要可以是数字或NA

【讨论】:

  • 非常有效的限制解释。
  • 谢谢@Gregor,我认为这是一个很好的更正。我将编辑我的问题以确保没有空白。并感谢您的解决方案!
【解决方案2】:

创建一个单独的data.frame,ds_blank,然后使用联合查询进行堆栈,然后通过名为index的临时变量对其进行排序。

library(magrittr)
na_index <- which(is.na(df$line))

ds_blank <- tibble::tibble(
  index   = na_index + .5,
  line    = rep(NA_real_          , length(na_index)),
  group   = rep("Not Applicable"  , length(na_index))
)

df <- df %>% 
  tibble::rowid_to_column("index") %>% 
  dplyr::union(ds_blank) %>% 
  dplyr::arrange(index) %>% 
  dplyr::select(-index)

结果

> df
  line          group
1    1    1.0 Group A
2    2    2.0 Group B
3   NA    3.0 Group C
4   NA Not Applicable
5    4    4.0 Group D
6    5   5.0  Group E
7   NA    6.0 Group F
8   NA Not Applicable
9    7    7.0 Group G

我想试试tibble::add_row(),但如果你指定一个位置,那显然不允许插入多行。

次要方法

...使用@Gregor 的技巧来使用for 循环。注意na_index 现在是反向排序的。

na_index <- sort(which(is.na(df$line)), decreasing = T)
for( i in na_index ) {
  df <- df %>% 
    tibble::add_row(
      line    = NA_integer_,
      group   = "Not Applicable",
      .after  = i
    )
}

【讨论】:

  • 你仍然可以使用 add_row 和 for 循环。
  • 我正在考虑这样做,但它似乎会产生很多开销,因为您需要在每个循环中找到下一个 NA 行,而不是能够从向量位置开始。
  • @Gregor,谢谢。我采纳了你的建议。我正在尝试purrr::map_dfr() 之类的方法,但没有考虑其他方法。
  • 倒退是行问题的巧妙解决方案。 which 将按顺序排列,因此您无需排序,只需 rev(which(...))。另一种可能的解决方案是,因为您知道每次只添加一行,所以继续前进并使用na_index = which(is.na(df$line)); add_row_index = na_index + seq_along(na_index) - 1。 (我更喜欢你的,只是放弃了另一种选择。)
【解决方案3】:

使用dplyr 可以实现解决方案。

方法很简单。添加代表row number 的列。取出行值为NA 的行。将group 替换为Not Applicable 并将row number 列增加0.5。绑定这两个数据框。

library(dplyr)

df %>% mutate(rownum = row_number()) %>% 
  bind_rows(., filter(., is.na(line)) %>% 
  mutate(group = "Not Applicable", rownum = rownum+.5)) %>% 
  arrange(rownum) %>%
  select(-rownum)


# line          group
# 1    1    1.0 Group A
# 2    2    2.0 Group B
# 3   NA    3.0 Group C
# 4   NA Not Applicable
# 5    4    4.0 Group D
# 6    5   5.0  Group E
# 7   NA    6.0 Group F
# 8   NA Not Applicable
# 9    7    7.0 Group G

@Gregor 提到的限制是有效的。数值列的值可以是NA,但不能是blank

【讨论】:

  • 谢谢@MKR!我必须增加行号吗?
  • @Connie 不是。但是为什么要将其保留为具有相同值的 2 行的默认排序行为。因此最好控制您的代码并增加 rownum。
  • 我明白了。谢谢! @MKR
【解决方案4】:

我觉得tidyr::uncount 函数可能也是您正在寻找的。只需在新列中用 2 标记 line==NA 行,我们将其称为n,然后uncount 将根据n 中的值复制每一行。通过对设置 NA 行 n == 2 的 ifelse 进行变异,我们基本上只在它们所在位置的正下方复制 NA 行,而不是在 df 的底部,因此需要 arrange。最后,dplyr::mutate_at 只是说如果line 和前一行的line(即滞后(线))都是 NA(这告诉我们只关注这些),则将group 设置为“不适用”重复的行)。我想这种方法也很有可扩展性!

library(tidyverse)
df %>%
    modify_if(is.factor, as.character) %>%
    mutate(n = ifelse(is.na(line), 2, 1)) %>%
    uncount(n) %>%
    mutate_at(vars(group), ~ifelse(is.na(line) & is.na(lag(line)), "Not Applicable", .))
#   line          group
# 1    1    1.0 Group A
# 2    2    2.0 Group B
# 3   NA    3.0 Group C
# 4   NA Not Applicable
# 5    4    4.0 Group D
# 6    5   5.0  Group E
# 7   NA    6.0 Group F
# 8   NA Not Applicable
# 9    7    7.0 Group G

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多