R:计算分组数据帧的第一行与当前行之间的距离

时间:2019-04-28 00:02:10

标签: r dplyr distance tidyverse tidyr

我需要计算数据帧中第一行与当前行之间的欧几里得距离。每行以(组,月份)为键,并具有值列表。在下面的玩具示例中,键是c(month,student),值在c(A,B)中。我想创建一个距离列C,它等于sqrt((A_i-A_1)^ 2 +(B_i-B_1)^ 2)。

到目前为止,我设法分散了数据并将每个组的第一个值拉到新列中。虽然我可以在玩具示例中手动创建公式,但在我的实际数据中,我有很多列而不是2列。我相信我可以在mutate_all中创建平方差,然后求和并取平方根其中,但到目前为止没有运气。

df <- data.frame(month=rep(1:3,2),
                 student=rep(c("Amy", "Bob"), each=3),
                 A=c(9, 6, 6, 8, 6, 9),
                 B=c(6, 2, 8, 5, 6, 7))

# Pull in each column's first values for each group
df %>% 
  group_by(student) %>% 
  mutate_all(list(first = first)) %>% 
# TODO: Calculate the distance, i.e. SQRT(sum_i[(x_i - x_1)^2]).

#Output:
  month student     A     B month_first A_first B_first
1     1 Amy         9     6           1       9       6
2     2 Amy         6     2           1       9       6
...

所需的输出:

#Output:
  month student     A     B month_first A_first B_first dist_from_first
1     1 Amy         9     6           1       9       6    0
2     2 Amy         6     2           1       9       6    5
...

3 个答案:

答案 0 :(得分:2)

这是使用紧凑型dplyr代码的另一种方法。可以用于任意数量的列

df %>% 
  select(-month) %>%
  group_by(student) %>% 
  mutate_each(function(x) (first(x) - x)^2) %>%
  ungroup() %>%
  mutate(euc.dist = sqrt(rowSums(select(., -1))))

# A tibble: 6 x 4
  student     A     B euc.dist
  <chr>   <dbl> <dbl>    <dbl>
1 Amy         0     0     0   
2 Amy         9    16     5   
3 Amy         9     4     3.61
4 Bob         0     0     0   
5 Bob         4     1     2.24
6 Bob         1     4     2.24

答案 1 :(得分:1)

编辑:使用联接添加了替代公式。我希望这种方法对于包含许多列要比较的非常宽的数据框会更快。

方法1:要获取大量列的欧几里得距离,一种方法是重新排列数据,以便每行显示一个月,一名学生和一个原始列(例如A或B) (在OP中),但随后两列分别代表当前月份值和第一个值。然后,我们可以对差异进行平方,并在所有列上进行分组以获得欧氏距离,即每个学生月的均方根/ RMS。

  library(tidyverse)
  df %>% 
    group_by(student) %>% 
    mutate_all(list(first = first)) %>%
    ungroup() %>%
  # gather into long form; make col show variant, col2 show orig column
  gather(col, val, -c(student, month, month_first)) %>%
  mutate(col2 = col %>% str_remove("_first")) %>% 
  mutate(col = if_else(col %>% str_ends("_first"),
                        "first",
                        "comparison")) %>% 
  spread(col, val) %>% 
  mutate(square_dif = (comparison - first)^2) %>%
  group_by(student, month) %>%
  summarize(RMS = sqrt(sum(square_dif)))

# A tibble: 6 x 3
# Groups:   student [2]
  student month   RMS
  <fct>   <int> <dbl>
1 Amy         1  0   
2 Amy         2  5   
3 Amy         3  3.61
4 Bob         1  0   
5 Bob         2  2.24
6 Bob         3  2.24

方法2。在这里,数据的长版本与每个学生最早一个月的版本结合在一起。

library(tidyverse)
df_long <- gather(df, col, val, -c(month, student))
df_long %>% left_join(df_long %>% 
              group_by(student) %>%
              top_n(-1, wt = month) %>%
              rename(first_val = val) %>% 
              select(-month),
            by = c("student", "col")) %>%
  mutate(square_dif = (val - first_val)^2) %>%
  group_by( student, month) %>%
  summarize(RMS = sqrt(sum(square_dif)))

# A tibble: 6 x 3
# Groups:   student [2]
  student month   RMS
  <fct>   <int> <dbl>
1 Amy         1  0   
2 Amy         2  5   
3 Amy         3  3.61
4 Bob         1  0   
5 Bob         2  2.24
6 Bob         3  2.24

答案 2 :(得分:0)

代替mutate_all调用,直接计算dist_from_first会更容易。我唯一不清楚的是group_by()语句中是否应包含月份。

library(tidyverse)

df <- tibble(month=rep(1:3,2),
                 student=rep(c("Amy", "Bob"), each=3),
                 A=c(9, 6, 6, 8, 6, 9),
                 B=c(6, 2, 8, 5, 6, 7))

df%>%
  group_by(student)%>%
  mutate(dist_from_first = sqrt((A - first(A))^2 + (B - first(B))^2))%>%
  ungroup()

# A tibble: 6 x 5
#  month student     A     B dist_from_first
#  <int> <chr>   <dbl> <dbl>           <dbl>
#1     1 Amy         9     6            0   
#2     2 Amy         6     2            5   
#3     3 Amy         6     8            3.61
#4     1 Bob         8     5            0   
#5     2 Bob         6     6            2.24
#6     3 Bob         9     7            2.24