В Бразилии есть игра Cartola, в которой оценивается производительность игроков за раунд. Давайте получим некоторую информацию об этих данных!

Введение

Сегодня я получу некоторую информацию, используя набор данных Cartola (https://www.kaggle.com/lgmoneda/cartola-fc-brasil-scouts?select=jogadores.csv).
Cartola — это игра, которая получает данные о Brasileirao (бразильский чемпионат по футболу), и пользователь может купить игрока и забить с его помощью.

Когда я увидел, как мои друзья играют в игру Cartola и принимают решения на основе инсайтов без статистики или данных. Итак, я подумал: «Могу ли я принять решение в Картоле на основе данных?»

Еще одна важная вещь — фильм Moneyball, где менеджер покупает игроков на основе статистики выхода в финал чемпионата. Можем ли мы сделать нечто подобное? Смотри, у меня есть цена, прибыль и оценка… Попробую.

Прежде чем я начну, я проверю некоторые детали, когда вы видите ZAG на графике, это означает защитник, проверьте примеры:
ATA = атакующие

ЗАГ = Защитники

ТИК = Менеджер

LAT = боковой

МЭИ = средний

ГОЛ = вратарь

Игроки с самым высоким средним показателем на позицию

Сначала я изучаю данные и смотрю на значения NaN.

import pandas as pd
cartolaDataframe = pd.read_csv(“/content/jogadores.csv”)
cartolaDataframe = cartolaDataframe[(cartolaDataframe[“ano”] == 2019)]
cartolaDataframe.rename(columns={“Nome”: “Name”}, inplace=True)
print(cartolaDataframe.shape)
print(cartolaDataframe.describe())
print(cartolaDataframe.isna().sum())

[8 строк х 27 столбцов]

Номер 0

Клуб 0

Предварительно 0

J 0

Медиа 0

ульт. Понт. 0

Вариасан 0

DS 8522

G 5045

A 5522

SG 5546

FS 1280

FF 2575

FD 3147

FT 7047

DD 7818

DP 8280

GC 8335

CV 7892

CA 3033

PP 8246

GS 7813

FC 1600

I 5428

PI 8522

родада 0

ано 0

RB 1692

PE 992

Много данных со значениями NaN. Ну, я думаю, что могу заменить на 0, потому что, если игрок не забил, то правильное число 0, а не NaN

cartolaDataframe = cartolaDataframe.fillna(0)

В Dataframe есть столбец под названием «Имя», из которого мы можем извлечь позицию каждого игрока. Например, Marinho (ATA), мы можем разделить и получить вашу позицию (ATA)

playersPosition = []
splitedNames = cartolaDataframe[“Name”].str.split(“ “)
for splitedName in splitedNames:
 for namePosition in splitedName:
 if “(“ in namePosition:
 namePosition = namePosition.replace(“(“, “”)
 namePosition = namePosition.replace(“)”, “”)
 playersPosition.append(namePosition)
cartolaDataframe[“posicao”] = playersPosition
cartolaDataframe

Я думаю, что это нормально генерировать игроков с самым высоким средним показателем.

playerAndPosition = {}
for nome in cartolaDataframe[“Name”].unique():
 lastMatchOfPlayer = cartolaDataframe[(cartolaDataframe[“Name”]==nome)].tail(1)
 lastMean = lastMatchOfPlayer[“Média”].tolist()[0]
 position = lastMatchOfPlayer[“posicao”].tolist()[0]
if position not in playerAndPosition:
 playerAndPosition[position] = {“name”: nome, “mean”: lastMean}
if lastMean > playerAndPosition[position][“mean”]:
 data = {“name”: nome, “mean”: lastMean}
 playerAndPosition[position] = data
 
playerAndPosition

Результат:
{‘ATA’: {‘media’: 10.73, ‘nome’: ‘Rodrygo (ATA)’},

«ГОЛ»: {«медиа»: 10.0, «имя»: «Александр (ГОЛ)»},

'LAT': {'media': 8.4, 'nome': 'Abner Felipe (LAT)'},

«MEI»: {«media»: 10.84, «nome»: «Arrascaeta (MEI)»},

«ТИК»: {«медиа»: 6.4, «номер»: «Андрей Лопес (ТИК)»},

«ZAG»: {«media»: 10.0, «nome»: «Rodrigues (ZAG)»}}

Команда с игроками с наивысшим средним баллом

Таким образом, мы можем получить каждую позицию. Почему бы не создать команду? Идти!!!!

Во-первых, я проверяю, у какой команды самый высокий средний балл.

Приведенный ниже код получает последнюю пунктуацию всех игроков команды и проверяет среднее значение этой команды.

#The team with the highest average 

highestAverageOfTeam = 0
teamNameWithHighestAverage = ""
for clubName in cartolaDataframe["Clube"].unique():
  mean = cartolaDataframe[(cartolaDataframe["Clube"] == clubName)]["Últ. Pont."].mean()
  if mean > highestAverageOfTeam:
    highestAverageOfTeam = mean
    teamNameWithHighestAverage = clubName
    

teamNameWithHighestAverage
print("club: ", teamNameWithHighestAverage, " highest average: ", teamNameWithHighestAverage)

клуб: Сантос самый высокий средний результат: 4.206107055961076

Первым шагом является создание кадра данных с необходимыми данными для расчета. Затем я выполняю процесс извлечения с кодом:

dataList = []
#filter the players with 20 matches or more
for name in cartolaDataframe["Name"].unique():
  totalGames = cartolaDataframe[(cartolaDataframe["Name"] == name)].shape[0]
  if totalGames > 20:
    mean = cartolaDataframe[(cartolaDataframe["Name"] == name)]["Últ. Pont."].mean()
    position = cartolaDataframe[(cartolaDataframe["Name"] == name)]["posicao"].to_list()[0]
    data = {"Name": name, "Mean": mean, "Position": position}
    dataList.append(data)

После этого я определяю три функции для извлечения столбцов фрейма данных.

def generateTeam4_4_2(df, nomeDaColunaParaOrdernar):
  attacker = df[(df["Position"] == "ATA")].sort_values(by=nomeDaColunaParaOrdernar).tail(2)
  defenser = df[(df["Position"] == "ZAG")].sort_values(by=nomeDaColunaParaOrdernar).tail(2)
  lateral = df[(df["Position"] == "LAT")].sort_values(by=nomeDaColunaParaOrdernar).tail(2)
  mid = df[(df["Position"] == "MEI")].sort_values(by=nomeDaColunaParaOrdernar).tail(4)
  manager = df[(df["Position"] == "TEC")].sort_values(by=nomeDaColunaParaOrdernar).tail(1)
  goalkeeper = df[(df["Position"] == "GOL")].sort_values(by=nomeDaColunaParaOrdernar).tail(1)
return attacker, defenser, lateral, mid, manager, goalkeeper
def getColumnDataBasedOnThePlayerName(playerName, dfToGetData, columnToGetData):
  columnData = dfToGetData[(dfToGetData["Name"] == playerName)][columnToGetData]
  columnData = columnData.replace(0, columnData.mean()).to_list()
  return columnData
def generateDataFromColumnPerRound(columnToGetRoundData, dataframe):
  data = {}
  for player in dataframe["Name"].unique():
    data[player] = getColumnDataBasedOnThePlayerName(player, dataframe, columnToGetRoundData)
dfPerRound = pd.DataFrame.from_dict(data,  orient='index').T
fillMean = lambda col: col.fillna(col.mean())
return dfPerRound.apply(fillMean, axis=0)

Теперь я построю другой фрейм данных

# Generate the team with the players with the highest average
dataFrameHighestAverage = pd.DataFrame(lista,
                  columns=['Name', 'Mean', 'Position'])
attackerHighestAverage, defenserHighestAverage, lateralHighestAverage, midHighestAverage, managerHighestAverage, goalkeeperHighestAverage = generateTeam4_4_2(dataFrameHighestAverage, "Mean")
playersWithHighestAverage = pd.concat([attackerHighestAverage, defenserHighestAverage, lateralHighestAverage, midHighestAverage, managerHighestAverage, goalkeeperHighestAverage])
rowsWithPlayers = np.isin(cartolaDataframe, playersWithHighestAverage["Name"].to_list())
cartolaDataframeplayersWithHighestAverage  = cartolaDataframe[rowsWithPlayers]
pontuacaoPorRodadaParaCadaJogadorComMediaAlta = {}
scorePerRoundTeamHighestAverage = generateDataFromColumnPerRound("Últ. Pont.", cartolaDataframeplayersWithHighestAverage).sum(axis=1)
print(scorePerRoundTeamHighestAverage.mean())

Причина заполнения средним значением заключалась в том, что игроки не играют во все игры, поэтому во фрейме данных есть игроки с 30 совпадениями, а другой - с 23 совпадениями. Таким образом, я предпочитаю заполнять средним значением.

Максимальное среднее значение, которое может иметь команда:

82.10831839758166

Но что, если мы используем ожидаемую ценность для создания команды? Будет ли он так же хорош, как команда с самым высоким средним показателем?

Теперь нам нужно создать столбец с ожидаемым значением!

cartolaDataframe.head()


cartolaDataframe["Lucro"] = cartolaDataframe["Últ. Pont."]/cartolaDataframe["Preço"]

dicitonaryList = []
#add expected value
for nome in cartolaDataframe["Name"].unique():
  totalGames = cartolaDataframe[(cartolaDataframe["Name"] == nome)].shape[0]
  if totalGames > 20:
    totaThatPlayerCanGain = cartolaDataframe[(cartolaDataframe["Lucro"] >= 0) & (cartolaDataframe["Name"] == nome)]["Lucro"].mean()
    totaThatPlayerCanLost = cartolaDataframe[(cartolaDataframe["Lucro"] <= 0) & (cartolaDataframe["Name"] == nome)]["Lucro"].mean()

    probabilityOfWin = len(cartolaDataframe[(cartolaDataframe["Lucro"] >= 0) & (cartolaDataframe["Name"] == nome)])/totalGames
    probabilityOfLose = len(cartolaDataframe[(cartolaDataframe["Lucro"] <= 0) & (cartolaDataframe["Name"] == nome)])/totalGames
    probabilityOfDraw = len(cartolaDataframe[(cartolaDataframe["Lucro"] == 0) & (cartolaDataframe["Name"] == nome)])/totalGames

    if probabilityOfDraw > 0:
      probabilityOfWin = probabilityOfWin - probabilityOfDraw
      probabilityOfLose = probabilityOfLose + probabilityOfDraw

    if pd.isnull(totaThatPlayerCanGain) or totaThatPlayerCanGain == 0:
      totaThatPlayerCanGain = 0
      probabilityOfWin = 0

    if pd.isnull(totaThatPlayerCanLost) or totaThatPlayerCanLost == 0:
      totaThatPlayerCanLost = 0

    ev = (totaThatPlayerCanGain * probabilityOfWin) - (totaThatPlayerCanLost * probabilityOfLose)
    position = cartolaDataframe[(cartolaDataframe["Name"] == nome)]["posicao"].to_list()[0]
    club = cartolaDataframe[(cartolaDataframe["Name"] == nome)]["Clube"].to_list()[0]
    price = cartolaDataframe[(cartolaDataframe["Name"] == nome)]["Preço"].mean()
    score = cartolaDataframe[(cartolaDataframe["Name"] == nome)]["Últ. Pont."].mean()
    data = {"Name": nome, "EV": ev, "Position": position, "Club": club, "Price": price, "Score": score, "ProbabilityOfWin": probabilityOfWin}
    dicitonaryList.append(data)

Первое, что я сделал, это создал столбец с прибылью для каждого игрока. Затем я создал расчет EV. Вероятность использования расчета EV, предположим, что средняя прибыль игрока равна 20, но у него есть 5% шансов на прибыль, а средний убыток равен 8, а его шанс проигрыша составляет 95%, тогда расчет будет выглядеть так:

-6.6 = 20*0,05–8 * 0.95

В долгосрочной перспективе это приносит вам убытки.

Теперь я повторю шаги, которые мы использовали для создания команды с самым высоким средним значением.

attacker, defenser, lateral, mid, manager, goalkeeper = generateTeam4_4_2(df, "EV")

playersWithHighestEV = pd.concat([attacker, defenser, lateral, mid, manager, goalkeeper])

rowsWithPlayers = np.isin(cartolaDataframe, playersWithHighestEV["Name"].to_list())

cartolaDataframePlayersWithHighestEV = cartolaDataframe[rowsWithPlayers]

scorePerRoundOfTimeEV = generateDataFromColumnPerRound("Últ. Pont.", cartolaDataframePlayersWithHighestEV).sum(axis=1)
print(scorePerRoundOfTimeEV.mean())

Средний результат: 31.115886491907343

Это хорошая разница между командой с самым высоким средним значением и EV команды, но EV команды действительно дешевое.

Мы идем строить график со строками, чтобы проверить обе производительности?

Мы удалили 1, потому что команда EV сыграла на 1 раунд меньше.

х = общее количество игр

import matplotlib.pyplot as plt

#We removed 1 because the EV team played 1 round less
x = list(range(0, len(scorePerRoundTeamHighestAverage) - 1))

xev = list(range(0, len(scorePerRoundOfTimeEV)))
plt.plot(x, scorePerRoundTeamHighestAverage.head(35), label="Time com as Médias mais altas")
plt.plot(xev, scorePerRoundOfTimeEV, label="Time com os EVs mais altas")

plt.legend()

Однако мы уже знаем выступления команд, и какова цена? Давайте создадим это сравнение?

Ну, мы знаем производительность, мы можем сравнить цену между ними. Будет ли разница действительно большой? Сколько баллов мы зарабатываем на общую сумму оплаченных?

# generate price per round TEAM HIGHEST AVERAGE
pricePerRoundTeamHighestAverage = generateDataFromColumnPerRound(“Preço”, cartolaDataframeplayersWithHighestAverage).sum(axis=1)
# generate price per round TEAM EV
pricePerRoundTeamEV = generateDataFromColumnPerRound(“Preço”, cartolaDataframePlayersWithHighestEV).sum(axis=1)
x = list(range(0, len(pricePerRoundTeamHighestAverage)-1))
xEv = list(range(0, len(pricePerRoundTeamEV)))
plt.figure(figsize=(15,8))
plt.title(“Comparando os preços”)
plt.bar(x, pricePerRoundTeamHighestAverage.head(35), label=”Time com as Médias mais altas”)
plt.bar(xEv, pricePerRoundTeamEV, label=”Time com os EVs mais altas”)
plt.legend()

Судя по всему, команда EV потратила на 40% меньше! Перейти, чтобы проверить некоторые отпечатки

print("AVERAGE PRICE TEAM EV: ", np.mean(pricePerRoundTeamEV), " MAX PRICE: ", np.max(pricePerRoundTeamEV), " MIN PRICE: ", np.min(pricePerRoundTeamEV), " MEDIAN: ", np.median(pricePerRoundTeamEV))
print("AVERAGE PRICE TEAM HIGHEST AVERAGE: ", np.mean(pricePerRoundTeamHighestAverage), " MAX PRICE: ", np.max(pricePerRoundTeamHighestAverage), " MIN PRICE: ", np.min(pricePerRoundTeamHighestAverage), " MEDIAN: ", np.median(pricePerRoundTeamHighestAverage))

СРЕДНЯЯ ЦЕНА КОМАНДЫ EV: 47,76917267496996

МАКСИМАЛЬНАЯ ЦЕНА: 56,440000000000005

МИНИМАЛЬНАЯ ЦЕНА: 29,7

МЕДИАНА: 49,85250898449377

СРЕДНЯЯ ЦЕНА КОМАНДЫ САМЫЙ ВЫСОКИЙ СРЕДНИЙ: 172.13556812434123

МАКСИМАЛЬНАЯ ЦЕНА: 186.38

МИНИМАЛЬНАЯ ЦЕНА: 140,55

МЕДИАНА: 176,39166219837134

Команда с самым высоким средним значением имеет цену на 3,59 дороже, чем команда EV, но

не набирает еще 3,59

Какой была бы лучшая команда, использующая 100 картолет?

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

Мы постараемся создать некоторую группировку и попытаться получить представление.

Во-первых, мы проверяем график с вероятностью того, что игрок наберет положительный результат, и вашим EV.

#Obtain averages during the entire Brazilian championship
dataOfTheBestTeam= []
for name in df["Name"].unique():
  price = np.mean(getColumnDataBasedOnThePlayerName(name, df, "Price"))
  score = np.mean(getColumnDataBasedOnThePlayerName(name, df, "Score"))
  ev = np.mean(getColumnDataBasedOnThePlayerName(name, df, "EV"))
  probabilityOfWin = np.mean(getColumnDataBasedOnThePlayerName(name, df, "ProbabilityOfWin"))
  position = df[(df["Name"] == name)]["Position"].to_list()[0]

  data = {"Score": score, "Price": price, "Name": name, "EV": ev, "Position": position, "ProbabilityOfWin": probabilityOfWin}
  dataOfTheBestTeam.append(data)
import seaborn as sns


dataFrameTheBestTeam = pd.DataFrame(dados, columns=["Score", "Price", "Name", "EV", "Position", "ProbabilityOfWin"])

dataFrameTheBestTeam["ScorePerPrice"] = dataFrameTheBestTeam["Score"]/dataFrameTheBestTeam["Price"]

plt.figure(figsize=(15,8))
sns.scatterplot('EV', 'ProbabilityOfWin', data=dataFrameTheBestTeam, hue="Position")

Но слишком сложно что-либо проверить, однако мы можем проверить, имеет ли менеджер более высокую вероятность забить.

Мы можем создать сетку, смотрите:

g = sns.FacetGrid(dataFrameTheBestTeam, col="Position", hue="Position", col_wrap=2)
g.map_dataframe(sns.scatterplot, x="EV", y="ProbabilityOfWin")
g.set_axis_labels("EV", "ProbabilityOfWin")
g.add_legend()

Я мог видеть, что даже с вероятностью высокого усиления EV низки. Также следует отметить, что у нападающих большие шансы на взятие ворот и вообще все выше 50%.

Ну и цена за пунктуацию?

plt.figure(figsize=(15,8))
sns.scatterplot('EV', 'Score', data=dataFrameTheBestTeam, hue="Position")

plt.figure(figsize=(15,8))
sns.scatterplot('Price', 'Score', data=dataFrameTheBestTeam, hue="Position")

Здесь я могу получить некоторые интересные инсайты… Дорогие защитники, менеджеры и вратарь обычно забивают больше, чем нападающие. Но это всего лишь визуализация, иди посмотри таблицу.

dataFrameParaMelhorTime[(dataFrameParaMelhorTime["Preço"] > 13) & (dataFrameParaMelhorTime["Preço"] < 16)].groupby(by="Posicao").max()

Здесь я смог заметить, что действительно стоит инвестировать в дорогие латерал, мид и гол.

Ну, так как я не знаю, как поставить эффективную кривую, как у Марковица, для этого случая, поэтому я буду злоупотреблять вычислениями и создам код для генерации 1 миллиона команд случайным образом.

данныеFrameTheBestTeam

dataList = []
for combination in range(0, 1000000):
  playersName = []

  attacker = dataFrameTheBestTeam[(dataFrameTheBestTeam['Position']=="ATA")].sample(n=2)
  attacker0 = attacker.iloc[0]
  attacker1 = attacker.iloc[1]

  manager = dataFrameTheBestTeam[(dataFrameTheBestTeam['Position']=="TEC")].sample(n=1).iloc[0]

  goalkeeper = dataFrameTheBestTeam[(dataFrameTheBestTeam['Position']=="GOL")].sample(n=1).iloc[0]

  mid = dataFrameTheBestTeam[(dataFrameTheBestTeam['Position']=="MEI")].sample(n=4)
  mid0 = mid.iloc[0]
  mid1 = mid.iloc[1]
  mid2 = mid.iloc[2]
  mid3 = mid.iloc[3]

  defenser = dataFrameTheBestTeam[(dataFrameTheBestTeam['Position']=="ZAG")].sample(n=2)
  defenser0 = defenser.iloc[0]
  defenser1 = defenser.iloc[1]

  lateral = dataFrameTheBestTeam[(dataFrameTheBestTeam['Position']=="LAT")].sample(n=2)
  lateral0 = lateral.iloc[0]
  lateral1 = lateral.iloc[1]

 
  teamPrice = attacker["Price"].sum() + manager["Price"].sum() + goalkeeper["Price"].sum() + mid["Price"].sum() + defenser["Price"].sum() + lateral["Price"].sum()

  if teamPrice >= 95 and teamPrice <= 100:
    playersName.extend([attacker0["Name"], attacker1["Name"], manager["Name"], goalkeeper["Name"], mid0["Name"], mid1["Name"], mid2["Name"], mid3["Name"], defenser0["Name"], defenser1["Name"], lateral0["Name"], lateral1["Name"]])
    teamScore = attacker["Score"].sum() + manager["Score"].sum() + goalkeeper["Score"].sum() + mid["Score"].sum() + defenser["Score"].sum() + lateral["Score"].sum()
    print("COMBINATION: ", combination, " TEAM SCORE: ",  teamScore, " PRICE: ", teamPrice)
    data = {"TeamPrice": teamPrice, "TeamScore": teamScore, "Players": playersName}
    dataList.append(data)
  

dataList
dataFrame100Cartoletas = pd.DataFrame(dataList, columns=["TeamPrice", "TeamScore", "Players"])

print("SHAPE: ", dataFrame100Cartoletas.shape)

print("MAX SCORE: ", dataFrame100Cartoletas["TeamScore"].max(), " AVERAGE SCORE: ", dataFrame100Cartoletas["TeamScore"].mean())
print("PLAYERS: ", dataFrame100Cartoletas.max()["Players"], " STANDARD DEVIATION: ", dataFrame100Cartoletas["TeamScore"].std())

ФОРМА: (103623, 3)

МАКСИМАЛЬНЫЙ СЧЕТ: 50,077461423149884

СРЕДНИЙ БАЛЛ: 38,21789270236684

ИГРОКИ: ['Йони Гонсалес (ATA)', 'Веллингтон Паулиста (ATA)', 'Вандерлей Люксембурго (TEC)', 'Эверсон (GOL)', 'Фабиньо (MEI)', 'Тиаго Гальхардо (MEI)', ' Патрик (MEI)», «Фелипе (MEI)», «Кинтеро (ZAG)», «Бруно Алвес (ZAG)», «Данило Авелар (LAT)», «Жильберто (LAT)»]

СТАНДАРТНОЕ ОТКЛОНЕНИЕ: 2,9366541312757555

У нас было 100 000 образцов со средним значением 38 точек при использовании 100 карт со стандартным отклонением 2,93. Что ж, судя по всему, наша команда по EV ниже среднего показателя команд со 100 картами, но тратит гораздо меньше.

Вывод

Команда EV для этого случая хоть и более прибыльна в долгосрочной перспективе, но не платит, потому что мы хотим набрать больше очков, даже рискуя больше и теряя картуши. Мы также увидели, что создание команды с очень высоким средним значением выходит за рамки, поскольку для этого требуется большая ценность картолет. Мы также обнаружили, что масштабирование латералей, защитников и мидов лучше, чем инвестиции в дорогих нападающих.

Очень важным замечанием является то, что в этих алгоритмах нет капитана команды, чей счет всегда равен 2x.