【问题标题】:mutate new column based on conditions in another row in R根据R中另一行的条件改变新列
【发布时间】:2020-10-19 21:44:51
【问题描述】:

我正在处理动物行为数据集,并尝试根据另一行中满足的条件创建一个新列(“环境”)。具体来说,如果行为介于行为“o_water”的开始/停止时间之间,我希望新列返回“water”,如果它超出这些界限,则返回“land”。如果不清楚,这里是一个最小的例子:

library(dplyr) 
library(magrittr)

otters <- data.frame(
  observation_id = 1,
  subject = 1,
  behavior = c("o_water", "swim", "float", "o_land", "walk", "o_water", "float"),
  start_time = c(1,1,2,6,6,11,11),
  stop_time = c(5,3,4,10,9,15,14)
)

#this does it, but manually. need to go over very large dataset and search for conditions
otters <- otters %>%
  group_by(subject, observation_id, behavior) %>%
  mutate(environment = ifelse((start_time >= 1 & stop_time <= 5) | 
                                (start_time >= 11 & stop_time <= 15), "water", "land"))

这是所需的输出。

Groups:   subject, observation_id, behavior [5]
  observation_id subject behavior start_time stop_time environment
           <dbl>   <dbl> <fct>         <dbl>     <dbl> <chr>      
1              1       1 o_water           1         5 water      
2              1       1 swim              1         3 water      
3              1       1 float             2         4 water      
4              1       1 o_land            6        10 land       
5              1       1 walk              6         9 land       
6              1       1 o_water          11        15 water      
7              1       1 float            11        14 water

第二组命令是我想要的,但我需要它来搜索并将其应用于整个数据集,而不是输入每个参数。分组是为了在适用的行上执行功能;在完整的数据集中,有多个主题和observation_id。

我尝试使用 when() 和 case_when() 无济于事,但我是 R 的新手,因此不胜感激!

对我所做的任何失误表示歉意。我无法在 stackoverflow 的其他地方找到类似的问题。

【问题讨论】:

  • 第一篇文章的准备工作很好。但是,由于您的管道准确地提供了所需的输出,我不清楚您想看到什么。
  • 谢谢,很抱歉造成混乱。基本上我试图找到一种方法来编写 ifelse() 而不必指定每个“o_water”的确切开头和结尾,因为实际数据集很大。 IE。有多个“o_water”等等,每个都有不同的observation_id和主题。
  • 我已经编辑了示例以包含“o_water”的第二个实例。因此,需要设置另一个条件以包含第二个“o_water”;我正在寻找一种方法来避免这种情况。基本上,“x 行为是否发生在 'o_water' 的开始/结束时间内?”

标签: r if-statement dplyr


【解决方案1】:

这是dplyr 的另一种方法,它也使用fuzzyjoin 包。

您可以将o_water 行为行与otters 分开,并将environment 指定为water。

然后,使用fuzzy_left_join,将o_water 行与其余数据合并,其中start_time 和end_time 介于o_water 范围之间。

environment 中剩余的 NA 将是未合并的行,可以是 land 或其他名称。

library(dplyr)
library(fuzzyjoin)

otters_water <- otters %>%
  filter(behavior == "o_water") %>%
  mutate(environment = "water") %>%
  select(-behavior)

otters %>%
  fuzzy_left_join(otters_water, 
                  by = c("subject", "observation_id", "start_time", "stop_time"), 
                  match_fun = list(`==`, `==`, `>=`, `<=`)) %>%
  replace_na(list(environment = "land")) %>%
  select(c(observation_id.x:stop_time.x, environment))

输出

  observation_id.x subject.x behavior start_time.x stop_time.x environment
1                1         1  o_water            1           5       water
2                1         1     swim            1           3       water
3                1         1    float            2           4       water
4                1         1   o_land            6          10        land
5                1         1     walk            6           9        land
6                1         1  o_water           11          15       water
7                1         1    float           11          14       water

【讨论】:

  • 您好,感谢您的精彩回答!这正是我想要的。
【解决方案2】:

你的问题做得很好。

我认为重新排列您的数据集在这里会有很大帮助。我建议重新排列它,以便每个时间点只有一个记录(可能是每个水獭),并且每个行为都有自己的列,二进制数据指示每个时间点是否发生该行为。

前几行发生了很多重新排列;我建议一次单步执行一行代码,看看每一行如何移动数据。

使用您提供的数据:

library(tidyverse)

otters_wide <- otters %>% 
  # first pivot to a longer form, so the time values are all in one column
  pivot_longer(cols = c("start_time", "stop_time"), names_to = "start_stop", values_to = "time", names_pattern = "(.*)_time") %>%
  # then pivot to w wider format, so each behavior has its own column. 
  pivot_wider(names_from = "behavior", values_from = "start_stop") %>%
  #Then arrange everything in order of time.
  arrange(time) %>%
  #Fill behavior columns downward
  fill(o_water, swim, float, o_land, walk) %>%
  #change all "start"s and the first "stop" in each series to "yes", and all other "stop"s to "no"
  mutate_at(.vars = c("o_water", "swim", "float", "o_land", "walk"), ~ if_else(. == "start" | lag(.) == "start", "yes", "no")) %>% 
  # this column is a little redundant now, but here's the water/land column, at last.
  mutate(environment = if_else(o_water == "yes", "water", "land"))
otters_wide

# A tibble: 11 x 9
   observation_id subject  time o_water swim  float o_land walk  environment
            <dbl>   <dbl> <dbl> <chr>   <chr> <chr> <chr>  <chr> <chr>      
 1              1       1     1 yes     yes   NA    NA     NA    water      
 2              1       1     2 yes     yes   yes   NA     NA    water      
 3              1       1     3 yes     yes   yes   NA     NA    water      
 4              1       1     4 yes     no    yes   NA     NA    water      
 5              1       1     5 yes     no    no    NA     NA    water      
 6              1       1     6 no      no    no    yes    yes   land       
 7              1       1     9 no      no    no    yes    yes   land       
 8              1       1    10 no      no    no    yes    no    land       
 9              1       1    11 yes     no    yes   no     no    water      
10              1       1    14 yes     no    yes   no     no    water      
11              1       1    15 yes     no    no    no     no    water

在这种替代格式中,您可以走得更远,包括未明确记录的时间点(例如,在此示例中为时间 7、8、12 和 13),一旦填写这些时间点,就会汇总诸如总时间之类的内容更直接地花费在每个行为上。

由于行为数据是二进制的,这些列当然可以包含逻辑数据而不是字符数据,但由于它们以字符数据开始,因此保持这种方式似乎最简单。

希望这会有所帮助。

【讨论】:

  • 感谢您如此详细的回答!我没有想过在这种情况下扩大它。虽然这不是我想要的,但我认为我可能会发现它很有用,具体取决于我使用数据的方式。我敢打赌,我会在某个时候使用这种方法。再次感谢!
【解决方案3】:

这是另一种方法,其中我将o_water的每个行为的所有start_和stop_times提取到一个列表中。由于o_water 有 2 个条目,因此这些列表有两个元素。 在purrr 的帮助下,我为每个条目创建了一个向量列表pu,这些向量跨越start_ 和stop_time。

purrr 还有助于找出otters 中的哪一行属于pu 中定义的序列。 l_true 是所有具有 FALSE/TRUE 的列的总和,如果有一个条目与时间序列匹配,则它包含 1,如果没有,则包含 0。

此列表绑定到otters,并使用ifelse 命名新列。

library(tidyverse)

otters <- data.frame(
  observation_id = 1,
  subject = 1,
  behavior = c("o_water", "swim", "float", "o_land", "walk", "o_water", "float"),
  start_time = c(1,1,2,6,6,11,11),
  stop_time = c(5,3,4,10,9,15,14)
)
# otters
# find all start_times of 'o_water'
otters %>%
  dplyr::filter(grepl('water', behavior)) %>% 
  select(ends_with('time')) %>% 
  `[[`(1) -> start
start
#> [1]  1 11

# find all stop_times
otters %>%
  dplyr::filter(grepl('water', behavior)) %>% 
  select(ends_with('time')) %>% 
  `[[`(2) -> stop
stop
#> [1]  5 15

# bring start and stop_times together in one
# list per 'o_water'
pu <- purrr::map2(start, stop, ~ .x : .y)
pu
#> [[1]]
#> [1] 1 2 3 4 5
#> 
#> [[2]]
#> [1] 11 12 13 14 15

# check with pu, if start_ and stop_time of each row is
# in pu, and combine the row of FALSE/TRUE into a list
l_true <- map_dfc(pu, ~ otters$start_time %in% . & otters$stop_time %in% .) %>% 
  mutate(l = rowSums(.[1:ncol(.)])) %>% `[[`(ncol(.))


otters %>% 
  cbind(., l_true) %>% 
  mutate(ev = ifelse(l_true == 1, 'water', 'land'))
#>   observation_id subject behavior start_time stop_time l_true    ev
#> 1              1       1  o_water          1         5      1 water
#> 2              1       1     swim          1         3      1 water
#> 3              1       1    float          2         4      1 water
#> 4              1       1   o_land          6        10      0  land
#> 5              1       1     walk          6         9      0  land
#> 6              1       1  o_water         11        15      1 water
#> 7              1       1    float         11        14      1 water

【讨论】:

    猜你喜欢
    • 2022-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多