Я написал некоторый код R для создания оптимального состава фэнтези-футбола (максимальное прогнозируемое количество набранных очков), ограниченного размерами, введенными пользователем, и черновым бюджетом на основе фрейма данных, называемого «игроки», который состоит из игрока, позиции, фэнтезийных очков и драфта. ценить.
Идея состоит в том, чтобы использовать этот инструмент перед драфтом (чтобы иметь в виду идеальный состав), а затем обновлять его прямо во время драфта, поскольку это никогда не идет по плану.
Оттуда я хочу иметь возможность удалять игроков из набора данных игроков (когда они выбираются другими) и добавлять игроков в свой состав по мере того, как я их выбираю (чтобы они появлялись в каждом будущем оптимальном составе). Я добавил функциональность, и кнопка удаления игрока, кажется, работает довольно хорошо (по какой-то причине состав исчезает, когда вы вводите нового игрока каждый раз, но затем снова появляется правильно после удаления нового игрока), но это определенно не так. похоже, правильно выбирают игрока в команду. Я определенно думаю, что это как-то связано с последним фрагментом кода перед запуском приложения, но у меня возникают проблемы с продумыванием логики.
Фрейм данных:
players <- structure(list(Player = c("Josh Allen", "Patrick Mahomes", "Justin Herbert",
"Lamar Jackson", "Kyler Murray", "Jalen Hurts", "Tom Brady",
"Dak Prescott", "Joe Burrow", "Russell Wilson", "Aaron Rodgers",
"Trey Lance", "Matthew Stafford", "Kirk Cousins", "Derek Carr",
"Tua Tagovailoa", "Justin Fields", "Trevor Lawrence", "Ryan Tannehill",
"Daniel Jones", "Matt Ryan", "Jameis Winston", "Carson Wentz",
"Mac Jones", "Jared Goff", "Zach Wilson", "Davis Mills", "Baker Mayfield",
"Marcus Mariota", "Deshaun Watson", "Mitchell Trubisky", "Geno Smith",
"Drew Lock", "Kenny Pickett", "Jacoby Brissett", "Desmond Ridder",
"Travis Kelce", "Mark Andrews", "Kyle Pitts", "Darren Waller",
"George Kittle", "Dalton Schultz", "T.J. Hockenson", "Dallas Goedert",
"Zach Ertz", "Dawson Knox", "Hunter Henry", "Mike Gesicki", "Pat Freiermuth",
"Cole Kmet", "Irv Smith Jr.", "Noah Fant", "Tyler Higbee", "David Njoku",
"Albert Okwuegbunam", "Gerald Everett", "Robert Tonyan", "Jonathan Taylor",
"Christian McCaffrey", "Derrick Henry", "Austin Ekeler", "Dalvin Cook",
"Joe Mixon", "Najee Harris", "Alvin Kamara", "D'Andre Swift",
"Leonard Fournette", "Saquon Barkley", "Aaron Jones", "Nick Chubb",
"James Conner", "Javonte Williams", "Ezekiel Elliott", "David Montgomery",
"Cam Akers", "Travis Etienne Jr.", "Breece Hall", "J.K. Dobbins",
"Josh Jacobs", "Antonio Gibson", "Elijah Mitchell", "AJ Dillon",
"Cordarrelle Patterson", "Damien Harris", "Miles Sanders", "Clyde Edwards-Helaire",
"Tony Pollard", "Devin Singletary", "Kareem Hunt", "Chase Edmonds",
"Rashaad Penny", "Rhamondre Stevenson", "Kenneth Walker III",
"Melvin Gordon III", "Darrell Henderson Jr.", "James Robinson",
"James Cook", "Dameon Pierce", "Michael Carter", "Jamaal Williams",
"Nyheim Hines", "J.D. McKissic", "Kenneth Gainwell", "Alexander Mattison",
"Isaiah Spiller", "Raheem Mostert", "Mark Ingram II", "Marlon Mack",
"Brian Robinson", "Gus Edwards", "Rex Burkhead", "Rachaad White",
"Khalil Herbert", "Damien Williams", "Tyler Allgeier", "D'Onta Foreman",
"Jerick McKinnon", "Cooper Kupp", "Justin Jefferson", "Ja'Marr Chase",
"Davante Adams", "Stefon Diggs", "Deebo Samuel", "CeeDee Lamb",
"Mike Evans", "Tyreek Hill", "Tee Higgins", "Keenan Allen", "DJ Moore",
"A.J. Brown", "Michael Pittman Jr.", "Mike Williams", "Brandin Cooks",
"Jaylen Waddle", "Diontae Johnson", "Terry McLaurin", "DK Metcalf",
"Courtland Sutton", "Amon-Ra St. Brown", "Darnell Mooney", "Allen Robinson II",
"Marquise Brown", "Amari Cooper", "Gabriel Davis", "Chris Godwin",
"Michael Thomas", "Jerry Jeudy", "Adam Thielen", "JuJu Smith-Schuster",
"Hunter Renfrow", "Rashod Bateman", "Elijah Moore", "Tyler Lockett",
"Christian Kirk", "Robert Woods", "DeVonta Smith", "Drake London",
"Allen Lazard", "Brandon Aiyuk", "Chase Claypool", "Kadarius Toney",
"Tyler Boyd", "Garrett Wilson", "DeVante Parker", "Chris Olave",
"Kenny Golladay", "Jakobi Meyers", "Russell Gage", "Marquez Valdes-Scantling",
"DeAndre Hopkins", "Marvin Jones Jr.", "Treylon Burks", "Michael Gallup",
"Robbie Anderson", "DJ Chark", "Jahan Dotson", "Mecole Hardman"
), Position = c("QB", "QB", "QB", "QB", "QB", "QB", "QB", "QB",
"QB", "QB", "QB", "QB", "QB", "QB", "QB", "QB", "QB", "QB", "QB",
"QB", "QB", "QB", "QB", "QB", "QB", "QB", "QB", "QB", "QB", "QB",
"QB", "QB", "QB", "QB", "QB", "QB", "TE", "TE", "TE", "TE", "TE",
"TE", "TE", "TE", "TE", "TE", "TE", "TE", "TE", "TE", "TE", "TE",
"TE", "TE", "TE", "TE", "TE", "RB", "RB", "RB", "RB", "RB", "RB",
"RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB",
"RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB",
"RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB",
"RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB",
"RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "RB", "WR",
"WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR",
"WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR",
"WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR",
"WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR",
"WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR", "WR",
"WR", "WR", "WR", "WR"), FantasyPoints = c(445, 410, 407, 348,
351, 359, 354, 364, 402, 368, 353, 347, 349, 335, 366, 325, 297,
313, 273, 283, 302, 284, 275, 296, 291, 0, 247, 286, 276, 0,
0, 0, 0, 269, 0, 0, 252, 231, 206, 171, 185, 177, 174, 169, 169,
171, 139, 131, 170, 170, 162, 129, 162, 119, 130, 126, 130, 340,
285, 260, 278, 277, 271, 277, 247, 271, 225, 247, 249, 230, 196,
268, 205, 199, 213, 231, 220, 177, 176, 159, 178, 185, 155, 181,
157, 190, 177, 164, 156, 166, 169, 179, 158, 129, 147, 99, 158,
176, 150, 100, 157, 128, 156, 124, 98, 95, 75, 90, 136, 80, 82,
143, 128, 0, 147, 97, 63, 326, 337, 308, 299, 269, 267, 271,
242, 243, 241, 239, 243, 242, 244, 209, 220, 233, 239, 221, 198,
221, 209, 220, 209, 218, 178, 224, 183, 186, 203, 188, 164, 207,
211, 202, 173, 188, 163, 199, 171, 181, 182, 140, 170, 175, 144,
142, 164, 147, 131, 170, 160, 182, 136, 153, 157, 152, 148, 175,
144), DraftValue = c(31, 23, 20, 15, 16, 14, 16, 11, 12, 10,
10, 3, 7, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 37, 34, 22, 20, 17, 16, 12, 11, 9, 6, 4, 4,
5, 5, 2, 2, 2, 1, 1, 1, 1, 56, 55, 44, 48, 38, 38, 40, 38, 36,
34, 34, 33, 27, 30, 28, 27, 23, 21, 23, 21, 19, 18, 10, 15, 16,
16, 12, 12, 14, 13, 10, 11, 12, 8, 9, 1, 6, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 56, 48, 41,
40, 37, 31, 34, 29, 30, 28, 28, 26, 24, 26, 23, 23, 22, 21, 20,
18, 19, 20, 17, 18, 17, 15, 15, 17, 17, 16, 16, 15, 15, 13, 12,
12, 12, 11, 9, 9, 9, 7, 5, 6, 4, 2, 2, 2, 1, 3, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1)), class = c("tbl_df", "tbl", "data.frame"), row.names = c(NA,
-177L))
и код ниже:
library(shiny)
library(lpSolve)
library(rsconnect)
# Define the UI for the app
ui <- fluidPage(
titlePanel("Fantasy Football Lineup Optimizer"),
sidebarLayout(
sidebarPanel(
numericInput("num_qb", "Enter the number of QBs:", 1, min = 1, max = 5),
numericInput("num_rb", "Enter the number of RBs:", 2, min = 1, max = 5),
numericInput("num_wr", "Enter the number of WRs:", 3, min = 1, max = 5),
numericInput("num_te", "Enter the number of TEs:", 1, min = 1, max = 5),
numericInput("num_value", "Enter your draft budget:", 200),
numericInput("num_players", "Adding in your flex spots, enter the total number of starters:", 9, min = 1, max = 15),
selectInput("remove", "Remove a player:", choices = c("",as.character(players$Player)), multiple = TRUE),
actionButton("update", "Update Team"),
selectInput("draft", "Draft Player", choices = c("",as.character(players$Player)), multiple = TRUE),
actionButton("draft_button", "Draft")
),
mainPanel(
tableOutput("team")
)
)
)
# Define the server logic
server <- function(input, output) {
players <- players
# Create a new column indicating the player's position
players$QB <- ifelse(players$Position == "QB", 1, 0)
players$RB <- ifelse(players$Position == "RB", 1, 0)
players$WR <- ifelse(players$Position == "WR", 1, 0)
players$TE <- ifelse(players$Position == "TE", 1, 0)
players$Total <- 1
rv <- reactiveValues(players=players)
# Define the objective function (maximize fantasy points)
obj <- players$FantasyPoints
# Define the constraints (position limits and draft value limit)
con <- reactive({
matrix(c(
# QB constraint
rv$players$QB,
# RB constraint
rv$players$RB,
# WR constraint
rv$players$WR,
# TE constraint
rv$players$TE,
# Draft value constraint
rv$players$DraftValue,
#Total players constraint
rv$players$Total
), ncol = nrow(rv$players), byrow = TRUE)
})
# Define the variables for the lp
dir <- c("< = ", rep("> = ",3),"< = ","< = ")
# Define the initial optimal lineup
initialLineup <- reactive({
rhs <- reactive({
c(input$num_qb, input$num_rb, input$num_wr, input$num_te, input$num_value, input$num_players)
})
result <- lp("max", obj, con(), dir, rhs(), all.bin = TRUE)
rv$players[result$solution == 1,]
})
# Show the updated optimal team in a table for any constraint change
output$team <- renderTable({
lineupResult()[, c("Player", "Position", "DraftValue", "FantasyPoints")]
})
# Define the function to run when the "update" button is pressed
updateLineup <- eventReactive(input$update, {
removedPlayer <- input$remove
rv$players <- rv$players[rv$players$Player != removedPlayer,]
obj <- rv$players$FantasyPoints
rhs <- reactive({
c(input$num_qb, input$num_rb, input$num_wr, input$num_te, input$num_value, input$num_players)
})
result <- lp("max", obj, con(), dir, rhs(), all.bin = TRUE)
rv$players[result$solution == 1,]
})
# Define the function to run when the "draft player" button is pressed
draftPlayer <- eventReactive(input$draft, {
draftedPlayer <- input$draft_player
draftedPlayers <- rv$players[rv$players$Player == draftedPlayer,]
rv$players <- rv$players[rv$players$Player != draftedPlayer,]
rv$draftedPlayers <- rbind(rv$draftedPlayers, draftedPlayers)
rhs <- reactive({
c(input$num_qb, input$num_rb, input$num_wr, input$num_te, input$num_value, input$num_players)
})
result <- lp("max", obj, con(), dir, rhs(), all.bin = TRUE)
rv$players[result$solution == 1,]
rv$players <- rbind(rv$players, rv$draftedPlayers)
})
# Show the updated optimal team in a table when the "update" button is pressed
output$team <- renderTable({
if (is.null(input$draft_player)) {
if (is.null(input$remove)) {
initialLineup()[, c("Player", "Position", "FantasyPoints", "DraftValue")]
} else {
updateLineup()[, c("Player", "Position", "FantasyPoints", "DraftValue")]
}
} else {
draftPlayer()[, c("Player", "Position", "FantasyPoints", "DraftValue")]
}
})
}
# Run the app
shinyApp(ui, server)
Я дал этому шанс. Я немного изменил ваш код, чтобы уменьшить его сложность, но сначала я укажу на несколько проблем, которые я обнаружил до того, как переписал его части.
Причина, по которой ваш состав исчезает, а затем появляется снова, связана с тем, как вы связываете свои renderTable
с вашими входами и eventReactives.
output$team
зависит от input$draft_player
и input$remove
. Во-первых, я не вижу input$draft_player
в вашем интерфейсе, поэтому я предполагаю, что это должно быть input$draft_button
. С учетом сказанного, при удалении игрока функция renderTable сначала становится недействительной, когда вы вводите игрока в поле выбора. Но updateLineup()
зависит от input$update
, поэтому он ничего не возвращает, пока вы не нажмете «Обновить команду». Тем самым вызывая задержку.
В вашем выражении draftPlayer
вы не установили obj
как новое значение rv$players$FantasyPoints
, поэтому вместо этого lp()
принимает значение obj в родительской среде, в которой есть полный набор игроков, и, следовательно, ошибка.
Я заметил еще одну вещь: в обеих функциях вы возвращаете rv$players[result$solution == 1,]
. Лично я думаю, что проблема в том, что вы всегда будете выводить наиболее оптимизированный полный состав, независимо от вашего собственного выбора драфта. Интуитивно я бы подумал, что вы захотите вернуть лучший состав, не включая позиции, которые вы уже выбрали. Итак, если QB и 2 WR были составлены. Тогда вы вернете состав только с 6 игроками, так как 3 уже выбраны.
Ниже я написал некоторый код, который учитывает этот последний фрагмент, а также уменьшает количество функций вывода. Это как раз то, что имело смысл для меня, и, возможно, я здесь не прав, но, надеюсь, это то же самое, что вы пытаетесь достичь!
Дайте знать, если у вас появятся вопросы.
Цель приведенного ниже кода состоит в том, чтобы включить ваши драфты вместе с оптимизированным выбором. Когда игрока выбирают на драфте, нам нужно сделать несколько вещей.
const.rhs
с новыми ограничениямиКогда игрок удаляется, он просто «удаляется» из пула игроков, и lp
будет работать без этого игрока.
С новыми ограничениями lp
вернет новый состав с количеством доступных игроков и позиций, которые еще не были выбраны с использованием измененного проекта бюджета. После того, как игрок выбран или удален, он будет удален из списка выбора, чтобы вы не могли случайно выбрать его в будущем.
В моей версии у меня есть несколько ограничений, о которых нужно знать. Для простоты вы можете удалить или выбрать только одного игрока за раз. Из-за того, как рассчитывается const.rhs
, сумма входных данных позиции ДОЛЖНА соответствовать общему количеству участников. Я знаю, что это может быть проблемой, так как после 5-го или 6-го раунда я буду обсуждать, брать ли RB или WR в зависимости от того, кто свободен. Также я бы посоветовал не изменять какие-либо данные выше, удалить игрока, как только начнется черновик, так как это, вероятно, все испортит.
library(shiny)
library(lpSolve)
library(purrr)
# Define the UI for the app
ui <- fluidPage(
titlePanel("Fantasy Football Lineup Optimizer"),
sidebarLayout(
sidebarPanel(
numericInput("num_qb", "Enter the number of QBs:", 1, min = 1, max = 5),
numericInput("num_rb", "Enter the number of RBs:", 3, min = 1, max = 5),
numericInput("num_wr", "Enter the number of WRs:", 3, min = 1, max = 5),
numericInput("num_te", "Enter the number of TEs:", 2, min = 1, max = 5),
numericInput("num_value", "Enter your draft budget:", 200),
numericInput("num_players", "Adding in your flex spots, enter the total number of starters:", 9, min = 1, max = 15),
selectInput("remove", "Remove a player:", choices = c("",as.character(players$Player)), multiple = FALSE),
selectInput("draft_player", "Draft Player", choices = c("",as.character(players$Player)), multiple = FALSE),
actionButton("update", "Update Lineup")
),
mainPanel(
tableOutput("team")
)
)
)
# Define the server logic
server <- function(input, output, session) {
players <- players
# New col to indicate if a player has been drafted
players$Drafted = "No"
# Create a new column indicating the player's position
players$QB <- ifelse(players$Position == "QB", 1, 0)
players$RB <- ifelse(players$Position == "RB", 1, 0)
players$WR <- ifelse(players$Position == "WR", 1, 0)
players$TE <- ifelse(players$Position == "TE", 1, 0)
players$Total <- 1
rv <- reactiveValues(players=players)
# Set up reactive table for lineup output
updateLineup = reactiveVal(NULL)
# Define the objective function (maximize fantasy points)
obj <- players$FantasyPoints
# Define the constraints (position limits and draft value limit)
con <- reactive({
matrix(c(
# QB constraint
rv$players$QB,
# RB constraint
rv$players$RB,
# WR constraint
rv$players$WR,
# TE constraint
rv$players$TE,
# Draft value constraint
rv$players$DraftValue,
#Total players constraint
rv$players$Total
), ncol = nrow(rv$players), byrow = TRUE)
})
# Define the variables for the lp
dir <- c("< = ", rep("> = ",3),"< = ","< = ")
# Define initial 'const.rhs'
init_rhs <- reactive({
list(
QB = input$num_qb,
RB = input$num_rb,
WR = input$num_wr,
TE = input$num_te,
n_val = input$num_value,
n_players = input$num_players
)
})
# Define reactive 'const.rhs'
rhs = reactiveValues(const = list())
# Run once to get the initial values and set them to reactiveValues
# so they can be changed later
observeEvent(init_rhs(),{
rhs$const = init_rhs()
}, once = TRUE)
# Define the initial optimal lineup
initialLineup <- reactive({
result <- lp("max", obj, con(), dir, init_rhs(), all.bin = TRUE)
rv$players[result$solution == 1,]
})
# Define the function to run when the "update" button is pressed
observeEvent(input$update, {
# Remove player here
if (input$remove != "") {
removedPlayer <- input$remove
rv$players <- rv$players[rv$players$Player != removedPlayer,]
obj <- rv$players$FantasyPoints
}
# Draft player
if (input$draft_player != "") {
draftedPlayer <- input$draft_player
draftedPlayer_details <- rv$players[rv$players$Player == draftedPlayer,]
draftedPlayer_details$Drafted = "Yes"
rv$players <- rv$players[rv$players$Player != draftedPlayer,]
rv$draftedPlayers <- rbind(rv$draftedPlayers, draftedPlayer_details)
obj <- rv$players$FantasyPoints # missing object
# Subtract constraints: position and n_players by 1 and draft budget by the players 'DraftValue'
# Necessary so "result" outputs a table with the remaining positions left
# otherwise it will return an entirely new lineup
rhs$const = purrr::imap(rhs$const, function(cs, nm) {
if (nm == draftedPlayer_details$Position) {cs = cs - 1}
if (nm == "n_players") {cs = cs - 1}
if (nm == "n_val") {cs = cs - draftedPlayer_details$DraftValue}
return(cs)
})
}
# Update select inputs to remove players after "Update Lineup" is clicked
if (input$remove != "" || input$draft_player != "") {
updateSelectInput(session, inputId = "remove", choices = c("",rv$players), selected = "")
updateSelectInput(session, inputId = "draft_player", choices = c("",rv$players), selected = "")
}
# Define result with updated arguments
result <- lp("max", obj, con(), dir, rhs$const, all.bin = TRUE)
# Assign new table to the reactiveVal 'updateLineup'
updateLineup(rbind(rv$draftedPlayers, rv$players[result$solution == 1,]))
})
output$team <- renderTable({
if (input$update == 0) {
initialLineup()[, c("Player", "Position", "FantasyPoints", "DraftValue", "Drafted")]
} else {
updateLineup()[, c("Player", "Position", "FantasyPoints", "DraftValue", "Drafted")]
}
})
}
# Run the app
shinyApp(ui, server)
я думаю, это потому, что я изначально не упомянул ограничение «flex», поэтому прошу прощения за это. Я строил требование гибкости с ограничением общего размера списка. Таким образом, общий размер состава будет больше, чем сумма qb+rb+wr+te в размере вашего ограничения гибкости, что, по общему признанию, является плохим дизайном.
Я немного занят на этой неделе, но посмотрю на следующей неделе, если не смогу заняться этим раньше. Я могу исправить проблему, из-за которой непреднамеренно выводится 9 игроков. Ограничение гибкости немного сложнее
спасибо, это очень помогает! это определенно большой шаг вперед, но я вижу две ошибки, на которые не могу ответить. Когда нажимается «обновить состав», он сбрасывает оптимальный состав до 9 игроков с этим кодом. Итак, если я открою приложение и изменю ограничения состава на 8 всего (1 qb, 2 rb, 2 wr, 1 te, 2 flex) и выберу friermuth, он покажет мне состав с 9 игроками, ВКЛЮЧАЯ еще один TE. Несмотря на то, что я выбираю игрока из оптимального состава, когда я выбираю фрирмута, он меняет оптимальный состав на включение TE по умолчанию, хотя у меня уже есть тот, который мне нужен.