Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

RPI

rpi.livemd

RPI

Mix.install([
  {:kino, "~> 0.12.3"},
  {:kino_explorer, "~> 0.1.19"}
])

Section

require Explorer.DataFrame, as: DF
require Explorer.Series, as: Series
df = DF.from_csv!("GameResults.csv")
defmodule RPI do
  def calc_wl(game_data, team, type) do
    Series.sum(Series.equal(game_data[type], team))
  end

  def filter_team(game_data, team) do
    DF.filter(game_data, winner == ^team or loser == ^team)
  end

  def filter_team_opp(game_data, team, opp) do
    DF.filter(filter_team(game_data, team), winner != ^opp and loser != ^opp)
  end

  def wp(winners, team) do
    Series.sum(Series.equal(winners, team)) / Series.count(winners)
  end

  def calc_wp(game_data, team) do
    games_played = filter_team(game_data, team)
    wp(games_played[:winner], team)
  end

  def calc_pd(game_data, team) do
    game_data
    |> filter_team(team)
    |> DF.mutate(
      pd: if(winner == ^team, 
        do: winner_score - loser_score, 
        else: loser_score - winner_score)
    )
    |> DF.pull("pd")
    |> Series.sum()
  end

  ## retaining this version as a reminder of alternative approach
  ## can use Enum.zip if only have two elements to zip
  # def calc_pd(game_data, team) do
  #   gp = filter_team(game_data)
  #   List.zip([
  #     Series.to_list(gp[:winner]),
  #     Series.to_list(gp[:winner_score]),
  #     Series.to_list(gp[:loser_score])
  #   ])
  #   |> Enum.map(fn {w, ws, ls} -> if(w == team, do: ws - ls, else: ls - ws) end)
  #   |> Series.from_list()
  #   |> Series.sum()
  # end

  def calc_owp(game_data, team) do
    game_data
    |> DF.filter(winner == ^team or loser == ^team)
    |> DF.mutate(opp: if(winner == ^team, do: loser, else: winner))
    |> DF.pull("opp")
    # transform is computationally expensive b/c of type conversion
    |> Series.transform(fn x -> calc_wp_owp(game_data, x, team) end)
    |> Series.mean()
  end

  def calc_wp_owp(game_data, team, opp) do
    games_played = filter_team_opp(game_data, team, opp)
    wp(games_played[:winner], team)
  end

  def calc_oowp(game_data, team) do
    game_data
    |> DF.filter(winner == ^team or loser == ^team)
    |> DF.mutate(opp: if(winner == ^team, do: loser, else: winner))
    |> DF.pull("opp")
    |> Series.transform(fn opp -> calc_owp(game_data, opp) end)
    |> Series.mean()
  end

  def calc_sos(game_data, team) do
    (2 * calc_owp(game_data, team) + calc_oowp(game_data, team)) / 3
  end

  def calc_rpi(game_data, team) do
    0.25 * calc_wp(game_data, team) +
      0.5 * calc_owp(game_data, team) +
      0.25 * calc_oowp(game_data, team)
  end
end
teams =
  Series.concat(df[:winner], df[:loser])
  |> Series.distinct()
  |> Series.to_enum()

DF.new(
  team: teams,
  win: Enum.map(teams, fn x -> RPI.calc_wl(df, x, :winner) end),
  loss: Enum.map(teams, fn x -> RPI.calc_wl(df, x, :loser) end),
  wp: Enum.map(teams, fn x -> RPI.calc_wp(df, x) end),
  pd: Enum.map(teams, fn x -> RPI.calc_pd(df, x) end),
  sos: Enum.map(teams, fn x -> RPI.calc_sos(df, x) end),
  rpi: Enum.map(teams, fn x -> RPI.calc_rpi(df, x) end)
)
|> DF.sort_by(desc: rpi)