Это оказывается сложнее, чем кажется. Определенно есть способы упростить найденное мной решение, но я собираюсь изложить его шаг за шагом, чтобы было ясно, что происходит.
Но сначала стоит уделить минутку, чтобы спросить, почему это сложно. Это выглядит как простая groupby()
задача, использующая .cumsum()
для суммирования выигрышей и проигрышей. И если бы вы просто хотели получить результаты для команд посещения, это было бы. Но ваша проблема в том, что вы хотите получить победы для команд, период , независимо от того, посещают они их или нет, дома.
Для этого я предлагаю разбить проблему на несколько шагов (всегда полезно делать, когда у вас есть какой-то проблемный код).
Основной ответ
Сначала разделите ваши данные на два кадра данных: один для команд-визитеров, а другой для домашних команд:
vdf = nba.set_index(['Season', 'Date', 'Visitor'])
vdf['win'] = np.where(vdf['H_PTS'] < vdf['V_PTS'], 1, 0)
vdf.index.names = ['Season', 'Date', 'Team']
hdf = nba.set_index(['Season', 'Date', 'Home'])
hdf['win'] = np.where(hdf['H_PTS'] > hdf['V_PTS'], 1, 0)
hdf.index.names = ['Season', 'Date', 'Team']
После разделения мы делаем две вещи для каждого кадра данных. Во-первых, мы создаем столбец win
, используя np.where()
(между прочим, совершенно pythonic. Я бы сказал, просто используйте логическое значение, но это не очень хорошо работает с .cumsum()
). Во-вторых, мы переименовываем индекс, поэтому вместо гостей и домашних команд мы просто говорим о командах. Это важно, потому что теперь мы собираемся снова объединить эти кадры данных в один:
df = pd.concat([vdf, hdf])
df['loss'] = 1 - df.win
df = df.sort_index(level=['Date'])
gr = df.groupby(level=['Season', 'Team'])
Мы можем вычислить loss
для команды как простую обратную величину win
с. Затем мы сортируем по Date
- поскольку ни одна команда не может играть в две игры одновременно, это должно быть хорошо. Затем мы сгруппируем по Season
и Team
, так как это группа, по которой вы хотите подсчитать результаты.
Теперь довольно просто суммировать выигрыши и проигрыши:
df['wins'] = gr.win.apply(lambda g: g.shift().cumsum().fillna(0))
df['losses'] = gr.loss.apply(lambda g: g.shift().cumsum().fillna(0))
Если вы хотите получить результаты, включая текущую игру, вы можете просто использовать .cumsum()
. Но так как вы хотите получить результаты предыдущей игры, нам нужно использовать .shift()
, а затем заполнить (теперь пустую) первую строку цифрой 0.
Но теперь у нас есть проблема: команды хозяев и гостей находятся в разных рядах! Чтобы это исправить, мы снова разделяем их на отдельные кадры данных и переименовываем столбцы, чтобы было ясно, говорим ли мы о гостевых или домашних записях.
vdf = df.dropna(subset=['Home'])[['wins', 'losses']].rename(columns=lambda c: 'V_' + c)
hdf = df.dropna(subset=['Visitor'])[['wins', 'losses']].rename(columns=lambda c: 'H_' + c)
Последний реальный шаг - это слияние с основным фреймом данных на основе имен Visitor
и Home
.
nba = nba.merge(vdf.astype(int), left_on=['Season', 'Date', 'Visitor'], right_index=True)
nba = nba.merge(hdf.astype(int), left_on=['Season', 'Date', 'Home'], right_index=True)
Наконец, мы можем (необязательно) создать формат W-L
, который вы упомянули в комментариях:
nba['V_winloss'] = nba['V_wins'].astype(str) + '-' + nba['V_losses'].astype(str)
nba['H_winloss'] = nba['H_wins'].astype(str) + '-' + nba['H_losses'].astype(str)
Готово! Упрощение этого (что определенно можно сделать) оставлено читателю в качестве упражнения.
Весь код в одном месте:
vdf = nba.set_index(['Season', 'Date', 'Visitor'])
vdf['win'] = np.where(vdf['H_PTS'] < vdf['V_PTS'], 1, 0)
vdf.index.names = ['Season', 'Date', 'Team']
hdf = nba.set_index(['Season', 'Date', 'Home'])
hdf['win'] = np.where(hdf['H_PTS'] > hdf['V_PTS'], 1, 0)
hdf.index.names = ['Season', 'Date', 'Team']
df = pd.concat([vdf, hdf])
df['loss'] = 1 - df.win
df['location'] = np.where(pd.notnull(df['Home']), 'h', 'v')
df = df.sort_index(level=['Date'])
gr = df.groupby(level=['Season', 'Team'])
df['wins'] = gr.win.apply(lambda g: g.shift().cumsum().fillna(0))
df['losses'] = gr.loss.apply(lambda g: g.shift().cumsum().fillna(0))
vdf = df.dropna(subset=['Home'])[['wins', 'losses']].rename(columns=lambda c: 'V_' + c)
hdf = df.dropna(subset=['Visitor'])[['wins', 'losses']].rename(columns=lambda c: 'H_' + c)
nba = nba.merge(vdf.astype(int), left_on=['Season', 'Date', 'Visitor'], right_index=True)
nba = nba.merge(hdf.astype(int), left_on=['Season', 'Date', 'Home'], right_index=True)
nba['V_winloss'] = nba['V_wins'].astype(str) + '-' + nba['V_losses'].astype(str)
nba['H_winloss'] = nba['H_wins'].astype(str) + '-' + nba['H_losses'].astype(str)
Пример (и несколько советов)
Всякий раз, когда у вас есть вопрос, постарайтесь создать как образец желаемого ввода, так и желаемого вывода, отформатированный таким образом, чтобы ответчики могли копировать и вставлять его непосредственно в свой код. Этот образец должен продемонстрировать ключевые требования для вашей задачи. В вашем случае это, вероятно, будет команда, которая играет как в домашней игре, так и в выездной игре, и несколько сезонов. Это полезное упражнение еще до того, как вы зададите вопрос - создание тестового стенда с базовыми кейсами позволяет легко проверить, работает ли код, который вы пишете, так, как задумано.
Я создал примерный фрейм данных, который включает эти функции. Обратите внимание, что я удалил столбцы, которые не имеют значения для рассматриваемой проблемы.
>>> nba
Season Date Visitor Home V_PTS H_PTS
0 1 1 A E 1 2
1 1 2 B E 1 0
2 1 3 C E 1 2
3 1 4 D E 1 0
4 1 5 E A 1 2
5 1 6 E B 1 0
6 1 7 E C 1 2
7 1 8 E D 1 0
8 2 9 A E 1 2
После расчета побед и поражений (df['losses'] = ...
) вы должны проверить, правильно ли накапливаются результаты команды:
>>> print df[['wins', 'losses']].sort_index(level=['Team', 'Date']).astype(int)
wins losses
Season Date Team
1 1 A 0 0
5 A 0 1
2 9 A 0 0
1 2 B 0 0
6 B 1 0
3 C 0 0
7 C 0 1
4 D 0 0
8 D 1 0
1 E 0 0
2 E 1 0
3 E 1 1
4 E 2 1
5 E 2 2
6 E 2 3
7 E 3 3
8 E 3 4
2 9 E 0 0
Тогда к концу вы получите это:
Season Date Visitor Home V_PTS H_PTS V_wins V_losses H_wins \
0 1 1 A E 1 2 0 0 0
1 1 2 B E 1 0 0 0 1
2 1 3 C E 1 2 0 0 1
3 1 4 D E 1 0 0 0 2
4 1 5 E A 1 2 2 2 0
5 1 6 E B 1 0 2 3 1
6 1 7 E C 1 2 3 3 0
7 1 8 E D 1 0 3 4 1
8 2 9 A E 1 2 0 0 0
H_losses V_winloss H_winloss
0 0 0-0 0-0
1 0 0-0 1-0
2 1 0-0 1-1
3 1 0-0 2-1
4 1 2-2 0-1
5 0 2-3 1-0
6 1 3-3 0-1
7 0 3-4 1-0
8 0 0-0 0-0
Вы можете просмотреть его, чтобы убедиться, что он соответствует тому, что вы хотите.