Выполняйте взвешенные вычисления на основе фрейма данных

#r #dataframe #dplyr #tidyverse #weighted

Вопрос:

Я должен рассчитать взвешенные баллы разных игроков в игре. У меня есть их индивидуальные оценки в первых трех столбцах и их веса в следующих трех столбцах, которые различаются в зависимости от игры (каждая строка представляет одну игру).

Может ли кто-нибудь помочь мне эффективно рассчитать взвешенные баллы?

 df <- data.frame(player1 = c(1,2,1), player2 = c(2,2,1), player3 = c(2,2,2), weightplayer1=c(0.7,0.8,0.7), weightplayer2 = c(0.6,0.1,0.6), weightplayer3=c(0.2,0.7,0.2))

#  player1 player2 player3 weightplayer1 weightplayer2 weightplayer3  
#       1       2       2      0.7           0.6            0.2
#       2       2       2      0.8           0.1            0.7
#       1       1       2      0.7           0.6            0.2
 

Мне нужен такой вывод, где в столбце weighted1scores указана сумма весов
игроков, набравших 1 балл, а в столбце weighted2scores указана сумма весов
игроков, набравших 2 балла. На самом деле у меня есть длинный список возможных оценок, поэтому на самом деле
в этом фрейме данных много столбцов (до 100 баллов и т. Д.).
Следовательно, эффективная формула/цикл были бы великолепны.

 
#  player1 player2 player3 weightplayer1 weightplayer2 weightplayer3  weighted1scores  weighted2scores
#       1       2       2      0.7           0.6            0.2              0.7              0.8  
#       2       2       2      0.8           0.1            0.7               0               1.6
#       1       1       2      0.7           0.6            0.2              1.3              0.2

 

Комментарии:

1. Обычно очень помогает, если ваша таблица нормализована до 3NF, чтобы иметь одну таблицу весов и другую таблицу оценок. Затем вы можете объединить таблицы по общему столбцу, например, player_id.

Ответ №1:

Вы можете нормализовать свои таблицы до 3NF, а затем объединить и агрегировать:

 library(tidyverse)

df <- data.frame(player1 = c(1, 2, 1), player2 = c(2, 2, 1), player3 = c(2, 2, 2), weightplayer1 = c(0.7, 0.8, 0.7), weightplayer2 = c(0.6, 0.1, 0.6), weightplayer3 = c(0.2, 0.7, 0.2))

scores <-
  df %>%
  as_tibble(rownames = "game") %>%
  pivot_longer(starts_with("player")) %>%
  transmute(
    game,
    player_id = name %>% str_extract("[0-9] $"),
    score = value
  )
scores
#> # A tibble: 9 × 3
#>   game  player_id score
#>   <chr> <chr>     <dbl>
#> 1 1     1             1
#> 2 1     2             2
#> 3 1     3             2
#> 4 2     1             2
#> 5 2     2             2
#> 6 2     3             2
#> 7 3     1             1
#> 8 3     2             1
#> 9 3     3             2

weights <-
  df %>%
  as_tibble(rownames = "game") %>%
  pivot_longer(starts_with("weight")) %>%
  transmute(
    game,
    player_id = name %>% str_extract("[0-9] $"),
    weight = value
  )
weights
#> # A tibble: 9 × 3
#>   game  player_id weight
#>   <chr> <chr>      <dbl>
#> 1 1     1            0.7
#> 2 1     2            0.6
#> 3 1     3            0.2
#> 4 2     1            0.8
#> 5 2     2            0.1
#> 6 2     3            0.7
#> 7 3     1            0.7
#> 8 3     2            0.6
#> 9 3     3            0.2

scores %>%
  inner_join(weights) %>%
  group_by(player_id, game) %>%
  summarise(weighted = sum(weight)) %>%
  pivot_wider(
    names_from = player_id,
    values_from = weighted,
    names_prefix = "weighted"
  )
#> Joining, by = c("game", "player_id")
#> `summarise()` has grouped output by 'player_id'. You can override using the `.groups` argument.
#> # A tibble: 3 × 4
#>   game  weighted1 weighted2 weighted3
#>   <chr>     <dbl>     <dbl>     <dbl>
#> 1 1           0.7       0.6       0.2
#> 2 2           0.8       0.1       0.7
#> 3 3           0.7       0.6       0.2
 

Создано 2021-09-20 пакетом reprex (v2.0.1)

Ответ №2:

Вот один из способов:

 df %>%
  mutate(game = row_number()) %>%
  pivot_longer(cols = starts_with('player'), names_to = "Player", values_to = "Score") %>%
  pivot_longer(cols = starts_with('weightplayer'), names_to = "WPlayer", values_to = "Weight") %>%
  filter(parse_number(Player) == parse_number(WPlayer)) %>%
  select(-WPlayer) %>%
  mutate(
    WeightedScore = Score * Weight
  )
 

Который вы могли бы оставить как есть, чтобы вернуть этот аккуратный столик

 # A tibble: 9 x 5
   game Player  Score Weight WeightedScore
  <int> <chr>   <dbl>  <dbl>         <dbl>
1     1 player1     1    0.7           0.7
2     1 player2     2    0.6           1.2
3     1 player3     2    0.2           0.4
4     2 player1     2    0.8           1.6
5     2 player2     2    0.1           0.2
6     2 player3     2    0.7           1.4
7     3 player1     1    0.7           0.7
8     3 player2     1    0.6           0.6
9     3 player3     2    0.2           0.4
 

Или продолжайте с:

 df %>%
  mutate(game = row_number()) %>%
  pivot_longer(cols = starts_with('player'), names_to = "Player", values_to = "Score") %>%
  pivot_longer(cols = starts_with('weightplayer'), names_to = "WPlayer", values_to = "Weight") %>%
  filter(parse_number(Player) == parse_number(WPlayer)) %>%
  select(-WPlayer) %>%
  mutate(
    WeightedScore = Score * Weight
  ) %>%
  pivot_longer(cols = c(Score, Weight, WeightedScore)) %>%
  mutate(name = paste(Player, name, sep = '_')) %>%
  pivot_wider(id = game)
 

Чтобы закончить на:

 # A tibble: 3 x 10
   game player1_Score player1_Weight player1_WeightedScore player2_Score player2_Weight player2_WeightedScore player3_Score player3_Weight player3_WeightedScore
  <int>         <dbl>          <dbl>                 <dbl>         <dbl>          <dbl>                 <dbl>         <dbl>          <dbl>                 <dbl>
1     1             1            0.7                   0.7             2            0.6                   1.2             2            0.2                   0.4
2     2             2            0.8                   1.6             2            0.1                   0.2             2            0.7                   1.4
3     3             1            0.7                   0.7             1            0.6                   0.6             2            0.2                   0.4
 

Ответ №3:

Другие решения до сих пор не воспроизводят ваши результаты, вероятно, потому, что вы не используете «веса» так, как мы ожидаем.

Это делает следующее :

 df <- data.frame(player1 = c(1,2,1), player2 = c(2,2,1), player3 = c(2,2,2), weightplayer1=c(0.7,0.8,0.7), weightplayer2 = c(0.6,0.1,0.6), weightplayer3=c(0.2,0.7,0.2))

library(tidyverse)
df <- df %>% mutate(game = row_number())
df %>% 
  pivot_longer(
    cols = player1:weightplayer3,
    names_to = c(".value", "player_id"),
    names_pattern = "(. )(\d)") %>% 
  rename(score = player, weight = weightplayer, player = player_id) %>% 
  group_by(game, score_col = paste0("weighted",score,"score")) %>% 
  summarize(weightedscore = sum(weight)) %>% 
  pivot_wider(names_from = score_col, values_from = weightedscore, values_fill = 0) %>% 
  left_join(df, .) %>% 
  select(-game) %>% 
  as.data.frame() # just to print all columns
#> `summarise()` has grouped output by 'game'. You can override using the `.groups` argument.
#> Joining, by = "game"
#>   player1 player2 player3 weightplayer1 weightplayer2 weightplayer3
#> 1       1       2       2           0.7           0.6           0.2
#> 2       2       2       2           0.8           0.1           0.7
#> 3       1       1       2           0.7           0.6           0.2
#>   weighted1score weighted2score
#> 1            0.7            0.8
#> 2            0.0            1.6
#> 3            1.3            0.2
 

Создано 2021-09-20 пакетом reprex (v2.0.1)

Сначала мы изменяем форму, чтобы привести данные в порядок (по одному наблюдению на строку), где в идеале мы должны оставаться до составления отчета, затем мы выполняем наш агрегированный расчет, изменяем его на неопрятный и возвращаем к исходному кадру data.frame .

Комментарии:

1. Большое спасибо!! Это было очень полезно. знаете ли вы, как я могу включить динамический подсчет, когда, если игрок набирает 0 очков, его вес исключается из расчета?

2. Вы можете заменить эти 0 на NA и использовать na.rm = TRUE в сумме

Ответ №4:

Вот еще один подход, который может быть немного более кратким:

 library(tidyverse)

df %>%
  mutate(output = pmap(., ~ {x <- c(...)[startsWith(names(df), "player")]
  y <- c(...)[startsWith(names(df), "weight")]
  as_tibble(cbind(sum(y[x == 1]), sum(y[x == 2])))})) %>%
  unnest_wider(output) %>%
  rename_with(~ gsub("V(\d )", "Weighted\1scores", .), starts_with("V"))

# A tibble: 3 x 8
  player1 player2 player3 weightplayer1 weightplayer2 weightplayer3 Weighted1scores
    <dbl>   <dbl>   <dbl>         <dbl>         <dbl>         <dbl>           <dbl>
1       1       2       2           0.7           0.6           0.2             0.7
2       2       2       2           0.8           0.1           0.7             0  
3       1       1       2           0.7           0.6           0.2             1.3
# ... with 1 more variable: Weighted2scores <dbl>