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

R: Calculate distance between first and current row of grouped dataframe

我需要计算数据框中第一行和当前行之间的欧氏距离。每行都按(组、月)键控并有一个值列表。在下面的示例中,键是 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
...

而不是 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

编辑:添加了使用连接的替代公式。我希望这种方法对于具有许多要比较的列的非常宽的数据框来说会快得多。

方法 1: 要获得大量列的欧氏距离,一种方法是重新排列数据,使每一行显示一个月、一名学生和一个原始列(例如 OP 中的 A 或 B),然后是代表当前月份值和第一个值的两列。然后我们可以对差异进行平方,并对所有列进行分组以获得欧氏距离,也就是每个学生月的均方根/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

这是另一种使用紧凑 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