Эффективное вычисление временных объектов с помощью pandas

#python #pandas #performance

#python #pandas #Производительность

Вопрос:

У меня есть следующий файл .csv :

 Match_idx,Date,Player_1,Player_2,Player_1_wins
0,2020-01-01,p1,p2,1
1,2020-01-02,p2,p3,0
2,2020-01-03,p3,p1,1
3,2020-01-04,p4,p1,1
  

Я хочу вычислить еще несколько столбцов, чтобы получить следующий выходной файл .csv :

 Match_idx,Date,Player_1,Player_2,Player_1_wins,Player_1_winrate,Player_2_winrate,Player_1_matches,Player_2_matches,Head_to_head
0,2020-01-01,p1,p2,1,0,0,0,0,0,''
1,2020-01-02,p2,p3,0,0,0,1,0,0,''
2,2020-01-03,p3,p1,1,1,1,1,1,0,''
3,2020-01-04,p4,p1,1,0,1/2,0,2,0,''
4,2020-01-05,p1,p3,0,1/2,2/2,3,2,'0'
5,2020-01-06,p3,p1,1,1/3,3/3,4,3,'11'
  

Семантика каждого столбца :

  • Match_idx , Date , Player_1 , Player_2 : простой
  • Player_1_wins : Player_1 выиграл матч? 1 : 0

Эти столбцы будут поддерживаться, и я хочу добавить эти :

  • Player_1_winrate : number_of_wins_for_player_1_before_this_one / number_of_matches_played_by_player_1_before_this_one

  • Player_2_winrate : то же, что и выше для player_2

  • Player_1_matches : number_of_matches_played_by_player_1_before_this_one

  • Player_2_matches : то же, что и выше для player_2

  • Head_to_head : результаты предыдущих сопоставлений между Player_1 и Player_2 . Кодируется как строка из {‘0’ и ‘1’} с ‘1’, если Player_1 матч выигран, иначе ‘0’.

Что я сделал

Я использую библиотеку pandas для работы с этим файлом. Наивный подход, о котором я думал, заключается в следующем: выберите каждый матч, проигранный или выигранный, сыгранный игроком, и упорядочите по дате. После этого для функции коэффициента выигрыша примените две следующие функции к совпадению.

 def get_matches_won_before_by_player(df: pd.DataFrame, player: str, before: str):
    mask_player_won = (
        ((df['Player_1_wins'] == 1) amp; (df['Player_1'] == player)) | 
        ((df['Player_1_wins'] == 0) amp; (df['Player_2'] == player))
    )

    req = df[(df['Date'] < before) amp; mask_player_won]
    req.sort_values(by='Date', inplace=True)
    return req

def get_matches_played_before_by_player(df: pd.DataFrame, player: str, before: str):
    mask_player_played = (
        (df['Player_1'] == player) | 
        (df['Player_2'] == player)
    )

    req = df[(df['Date'] < before) amp; mask_player_played]
    req.sort_values(by='Date', inplace=True)
    return req
  

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

Что я хотел бы сделать

Как я могу эффективно вычислять свои функции, используя только последнее совпадение каждого игрока в данном матче? Например, обновление коэффициента выигрыша каждого игрока может быть выполнено с помощью следующей логики :

  1. Инициализируйте каждый столбец равным 0.
  2. Обновите коэффициент выигрыша следующим образом: (M / M 1) (W / N 1), с M текущим коэффициентом выигрыша, N текущим количеством сыгранных матчей и W = 1, если игрок выиграл, иначе 0.

Любая помощь или идея по организации такого процесса очень ценится.

Ответ №1:

Я пытался работать с сериями, чтобы решение работало быстро. Я объясню с помощью комментариев в коде.

 # to return head to head
strp1gw = ""
def get_head_to_head(s):
    global strp1gw
    strp1gw  =str(s)
    return strp1gw

(
    df = df
    .assign(
        # this is player 1 all wins before but to avoid creating extra columns I named it as Player_1_winrate to replace it with rate as you dont need cumulative sum of wins
        Player_1_winrate = lambda x: x['Player_1_wins'].cumsum(),
        # if player 1 played?
        Player_1_matches = lambda x: np.where((x['Player_1'] =='p1') | (x['Player_2'] == 'p1'),1,0)
    )
    # this is number of matches played by player 1 before this one
    .assign(Player_1_matches = lambda x: x['Player_1_matches'].cumsum())
    # the player 1 winrate
    .assign(Player_1_winrate = lambda x: x['Player_1_winrate']/x['Player_1_matches'])
    # same for player 2 but you didnt mention how to compute Player_2_wins
    .assign(
        Player_2_winrate = lambda x: x['Player_2_wins'].cumsum(),
        Player_2_matches = lambda x: np.where((x['Player_1'] =='p2') | (x['Player_2'] == 'p2'),1,0)
    )
    .assign(Player_2_matches = lambda x: x['Player_2_matches'].cumsum())
    .assign(Player_2_winrate = lambda x: x['Player_2_winrate']/x['Player_2_matches'])
    # to apply function to get head to head value
    .assign(Head_to_head=lambda x: x['Player_1_wins'].apply(lambda s: get_head_to_head(s)))
)