#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>