[2]
import pandas as pd
import numpy as np
import pulp
import math
import toolz as tz
[12]
from typing import List, Callable, Dict


def assign_split_col(df: pd.DataFrame, col: str, name_list: List[str], pat: str = None):
    df = df.copy()
    split_col = df[col].str.split(pat, expand=True)

    return df.assign(
        **dict(
            zip(name_list, [split_col.iloc[:, x] for x in range(split_col.shape[1])])
        )
    )


def create_position_constraint(
    df: pd.DataFrame,
    inlusion_dct: Dict[str, pulp.pulp.LpVariable],
    position: str,
    constraint: Callable,
) -> pulp.pulp.LpConstraint:

    pulp_sum = pulp.lpSum(
        tz.dicttoolz.keyfilter(
            lambda x: x in df.query(f"position == '{position}'")["name"].to_list(),
            inlusion_dct,
        )
    )
    return constraint(pulp_sum)


# create_position_constraint(edited, include, "QB", lambda x: x==3)


def create_flexible_position_constraint(
    df: pd.DataFrame,
    inlusion_dct: Dict[str, pulp.pulp.LpVariable],
    positions: List[str],
    constraint: Callable,
) -> pulp.pulp.LpConstraint:

    pulp_sum = pulp.lpSum(
        tz.dicttoolz.keyfilter(
            lambda x: x
            in df.loc[lambda y: y["position"].isin(positions), "name"].to_list(),
            inlusion_dct,
        )
    )
    return constraint(pulp_sum)


# create_flexible_position_constraint(edited, include,
#                                    ['RB', 'WR', 'TE'], lambda x: x==3)


def get_total_for_players(
    df: pd.DataFrame,
    inlusion_dct: Dict[str, pulp.pulp.LpVariable],
    col: str,
    multiplier: float = 1,
) -> pulp.pulp.LpAffineExpression:

    players = df["name"].to_list()

    ser = df.set_index("name")[col]

    return pulp.lpSum(
        [inlusion_dct[player] * ser[player] * multiplier for player in players]
    )


# get_total_for_players(edited, include, "salary")
# get_total_for_players(edited, ImTheCaptainNow, "salary", 1.5)


def epl_single_game(df: pd.DataFrame, salary: int, best_value: int) -> List[str]:
    # Defender isn't always the last one returned.  Gotta look it up.

    players = df["name"].to_list()

    include = pulp.LpVariable.dict(
        "include", [player for player in players], lowBound=0, upBound=1, cat="Integer"
    )

    # Captain can only be a FWD or MID
    captain_df = df.loc[lambda y: y["position"].isin(["FWD", "MID"])]
    captain_players = captain_df["name"].to_list()

    ImTheCaptainNow = pulp.LpVariable.dict(
        "ImTheCaptainNow",
        [player for player in captain_players],
        lowBound=0,
        upBound=1,
        cat="Integer",
    )

    prob = pulp.LpProblem("optimal_lineup", pulp.LpMaximize)

    prob += pulp.lpSum(
        get_total_for_players(df, include, "FPPG")
        + get_total_for_players(captain_df, ImTheCaptainNow, "FPPG", 1.5)
    )

    prob += pulp.lpSum([include[player] for player in players]) == 4

    prob += pulp.lpSum([ImTheCaptainNow[player] for player in captain_players]) == 1

    for player in captain_players:
        prob += pulp.lpSum(include[player] + ImTheCaptainNow[player]) <= 1.0

    prob += create_position_constraint(df, include, "DEF", lambda x: x == 1)

    prob += create_flexible_position_constraint(
        df, include, ["FWD", "MID"], lambda x: x == 3
    )

    prob += (
        pulp.lpSum(
            get_total_for_players(df, include, "salary")
            + get_total_for_players(captain_df, ImTheCaptainNow, "salary")
        )
        <= salary
    )
    prob += (
        pulp.lpSum(
            get_total_for_players(df, include, "FPPG")
            + get_total_for_players(captain_df, ImTheCaptainNow, "FPPG", 1.5)
        )
        <= best_value
    )

    prob.solve()
    
    
    #Format it the way the CSV upload function on FanDuel expects it
    captain = (
        tz.itertoolz.first(
            tz.dicttoolz.itemfilter(
                lambda item: item[1].varValue == 1, ImTheCaptainNow
            ).keys()
        ),
        "Captain",
    )

    rest_of_team = list(
        tz.dicttoolz.itemfilter(lambda item: item[1].varValue == 1, include).keys()
    )
    players_and_positions = df.set_index("name").loc[rest_of_team, "position"]

    fwds_and_mids = players_and_positions.loc[lambda x: x.isin(["MID", "FWD"])]

    defense = players_and_positions.loc[lambda x: x == "DEF"]

    whole_team = tz.itertoolz.concat(
        [(captain,), fwds_and_mids.items(), defense.items()]
    )

    return pd.DataFrame(whole_team).T.iloc[::-1]
[4]
filename ="FanDuel-EPL-2020-01-29-42993-players-list.csv"
raw_df = pd.read_csv(filename)
[6]
df = (
    raw_df.loc[lambda x: x["Injury Indicator"].isna()]
    .pipe(assign_split_col, "Id", ["slate_id", "player_id"], pat="-")
    .assign(name=lambda x: x["First Name"] + " " + x["Last Name"])
    .rename(columns={"Position": "position", "Salary": "salary"})
    .loc[:, ["slate_id", "player_id", "salary", "position", "name", "FPPG"]]
)
#Injuries don't necessarily show up in the Indicator - do have to doublecheck
[13]
lineup = epl_single_game(df, 50, 1000)
lineup
0 1 2 3 4
1 Captain FWD MID MID DEF
0 Mohamed Salah Ghaly Roberto Firmino Barbosa de Oliveira Declan Rice Naby Keita Trent Alexander-Arnold
[8]
df.set_index("name").loc[lineup, "FPPG"].sum()
89.95956473972485
[9]
#Let's see the Expected Value if I drop Naby, who might not play tomorrow
[10]
lineup2 = epl_single_game(df[lambda x: x["name"]!="Naby Keita"], 50, 1000)
lineup2
['Mohamed Salah Ghaly',
 'Roberto Firmino Barbosa de Oliveira',
 'Trent Alexander-Arnold',
 'Declan Rice',
 'Georginio Wijnaldum']
[11]
df.set_index("name").loc[lineup2, "FPPG"].sum()
86.03956504490064
[ ]