Я пытаюсь сделать в pandas то, что сделал бы с коррелированным подзапросом в SQL, и не смог найти не итеративное и / или более быстрое решение. Рассмотрим (гораздо больший) фрейм данных игроков, игр и статистики для этих игр, который можно определить следующим образом:
import pandas as pd
def make_mock_df():
mock_data = {
"player": ["Bob", "Bob", "Bob", "Bob", "Bob", "Jim", "Jim", "Jim", "Jim", "Jim"],
"game_id": ["red", "red", "green", "green", "blue", "red", "yellow", "yellow", "blue", "blue"],
"stat_id": [1, 2, 1, 2, 1, 1, 1, 2, 1, 2],
"value": [1, 3, 2, 4, 3, 1, 1, 2, 4, 2],
}
return pd.DataFrame(mock_data)
На самом деле идентификатор игры будет UUID, и данные уже будут сортировать сначала по игроку, а затем по дате игры, недавно-сначала.
Я хочу создать информационный фрейм, который будет усреднять каждый тип статистики за последние N игр на игрока. Это становится непросто, поскольку не каждый игрок играет в каждую игру (по факту насмешек в данных), и поэтому я сделал бы соответствующий подзапрос в SQL. До сих пор это итеративное решение, которое разбивает информационный кадр по игроку, затем объединяет каждый ограниченный информационный кадр игрока, чтобы перестроить целое:
def filter_last_n_games(df, n_games):
last_n_games_per_player = pd.DataFrame()
for _, player_group in df.groupby("player"):
# this works because games are sorted already
# and appear sorted in the unique() series as well
last_n_games = player_group.game_id.unique()[0:n_games]
# can't do nlargest because games have string IDs
last_n_mask = player_group.game_id.isin(last_n_games)
player_last_n = player_group[last_n_mask]
# build up last N games df piece by piece...
last_n_games_per_player = pd.concat(
[last_n_games_per_player, player_last_n]
)
return last_n_games_per_player
def avg_stats_last_n_games(df, n_games):
last_n_games_per_player = filter_last_n_games(df, n_games)
# calculate averages per game (easy part)
last_n_avgs = (
last_n_games_per_player
.groupby(["player", "stat_id"])
.mean()
.filter(["player", "stats_id", "value"])
.rename(columns={"value": "avg_value"})
)
return last_n_avgs
И приводит к выводу как таковому:
stats_df = make_mock_df()
avg_stats_last_n_games(stats_df, 2)
>>> avg_value
>>> player stat_id
>>> Bob 1 1.5
>>> 2 3.5
>>> Jim 1 1.0
>>> 2 2.0
Есть ли лучшая замена функции filter_last_n_games()
, которая бы в полной мере воспользовалась возможностями pandas 'и позволила бы мне отказаться от for-l oop?