summer_practice/main.py
2026-06-25 19:16:00 +03:00

216 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys # для аргументов командной строки
import os # чтобы достать имя файла из пути
import pandas as pd
import numpy as np
def make_conf(west, east):
d = {}
d.update({team: 'Запад' for team in west})
d.update({team: 'Восток' for team in east})
return d
# ключ - это код сезона (например, 2018_19)
CONFERENCES = {
'2018_19': make_conf(
west=['ЦСКА', 'СКА', 'Локомотив', 'Йокерит', 'Динамо М', 'ХК Сочи',
'Спартак', 'Витязь', 'Динамо Р', 'Северсталь', 'Динамо Мн', 'Слован'],
east=['Автомобилист', 'Барыс', 'Металлург Мг', 'Авангард', 'Ак Барс',
'Салават Юлаев', 'Торпедо НН', 'Трактор', 'Сибирь', 'Нефтехимик',
'Куньлунь РС', 'Адмирал', 'Амур'],
),
'2019_20': make_conf(
west=['ЦСКА', 'СКА', 'Йокерит', 'Динамо М', 'Спартак', 'Локомотив',
'Витязь', 'Торпедо НН', 'ХК Сочи', 'Северсталь', 'Динамо Р', 'Динамо Мн'],
east=['Ак Барс', 'Барыс', 'Авангард', 'Автомобилист', 'Сибирь',
'Салават Юлаев', 'Металлург Мг', 'Нефтехимик', 'Амур', 'Куньлунь РС',
'Трактор', 'Адмирал'],
),
'2020_21': make_conf(
west=['ЦСКА', 'СКА', 'Динамо М', 'Локомотив', 'Йокерит', 'Северсталь',
'Динамо Мн', 'Спартак', 'Витязь', 'ХК Сочи', 'Динамо Р'],
east=['Ак Барс', 'Авангард', 'Металлург Мг', 'Салават Юлаев', 'Трактор',
'Барыс', 'Автомобилист', 'Торпедо НН', 'Сибирь', 'Амур', 'Нефтехимик',
'Куньлунь РС'],
),
'2021_22': make_conf(
west=['СКА', 'Йокерит', 'ЦСКА', 'Динамо М', 'Северсталь', 'Локомотив',
'Спартак', 'Динамо Мн', 'Торпедо', 'Витязь', 'ХК Сочи', 'Динамо Р'],
east=['Металлург Мг', 'Трактор', 'Салават Юлаев', 'Ак Барс', 'Авангард',
'Сибирь', 'Нефтехимик', 'Барыс', 'Автомобилист', 'Амур', 'Адмирал',
'Куньлунь РС'],
),
}
'''
Шаг 1.
Формат счёта: 3:1 (хозяева забили 3, гости 1).
Если период не игрался (нет овертайма или буллитов), в файле стоит ":" - пустые числа.
df[col].str.split(':', expand=True) применяет строковую операцию split(':')
сразу ко всем строкам столбца и раскладывает результат в два столбца (expand=True).
pd.to_numeric превращает текст "3" в число 3. Параметр errors='coerce' означает:
если это не число (например, пустая строка), сделай NaN, а fillna(0) заменяет такие
пустые места на 0. В итоге не сыгранный период считается как "0:0".
'''
def parse_score(df, col):
parts = df[col].str.split(':', expand=True)
home = pd.to_numeric(parts[0], errors='coerce').fillna(0)
away = pd.to_numeric(parts[1], errors='coerce').fillna(0)
return home, away
# Шаг 2. Функция, которая из таблицы матчей строит таблицу команд.
def build_standings(df, conf_map):
# Достаём счёт по периодам и т.д.
h1, a1 = parse_score(df, 'Период_1') # голы хозяев/гостей в 1 периоде
h2, a2 = parse_score(df, 'Период_2') # второй
h3, a3 = parse_score(df, 'Период_3') # третий
oth, ota = parse_score(df, 'Овертайм') # овертайм (0/0, если его не было)
soh, soa = parse_score(df, 'Буллиты') # серия буллитов (0/0, если её не было)
# Голы в основное время (сумма трёх периодов), складываются поэлементно, строка к строке
reg_h = h1 + h2 + h3 # сколько хозяева забили за три периода
reg_a = a1 + a2 + a3 # сколько гости забили за три периода
# Определяем ТИП исхода каждого матча через сравнение столбцов
# (любое сравнение возвращает столбец False/True)
tie = (reg_h == reg_a) # True там, где после 3 периодов была ничья
win_home_reg = (reg_h > reg_a) # хозяева победили в основное время
win_away_reg = (reg_a > reg_h) # гости победили в основное время
win_home_ot = tie & (oth > ota) # ничья в основное + хозяева забили в ОТ = победа в ОТ
win_away_ot = tie & (ota > oth) # ничья в основное + гости забили в ОТ
win_home_so = tie & (oth == ota) & (soh > soa) # ОТ ничейный -> буллиты выиграли хозяева
win_away_so = tie & (oth == ota) & (soa > soh) # буллиты выиграли гости
'''
Считаем заброшенные шайбы для итоговой таблицы.
К голам в основное время прибавляем голы овертайма, а победителю серии буллитов
добавляем ровно 1 решающую шайбу. astype(int) переводит True/False в 1/0.
'''
goals_home = reg_h + oth + win_home_so.astype(int) # все шайбы хозяев
goals_away = reg_a + ota + win_away_so.astype(int) # все шайбы гостей
'''
Конвертируем "по одной строке на матч" -> "по две строки на матч".
В каждом матче участвует 2 команды. Делаем две таблицы:
home - взгляд со стороны хозяев, away - со стороны гостей,
а потом склеиваем и группируем по названию команды.
Столбцы В, ВО, ВБ, ПБ, ПО, П - это булевы маски (выиграл/проиграл).
'''
home = pd.DataFrame({
'Команда': df['Команда_1'], # имя хозяев
'В': win_home_reg, # победа в основное время
'ВО': win_home_ot, # победа в овертайме
'ВБ': win_home_so, # победа по буллитам
'ПБ': win_away_so, # поражение по буллитам (гости выиграли буллиты)
'ПО': win_away_ot, # поражение в овертайме
'П': win_away_reg, # поражение в основное время
'ГЗ': goals_home, # голы, забитые хозяевами
'ГП': goals_away, # голы, пропущенные (забитые гостями)
})
away = pd.DataFrame({
'Команда': df['Команда_2'],
'В': win_away_reg,
'ВО': win_away_ot,
'ВБ': win_away_so,
'ПБ': win_home_so,
'ПО': win_home_ot,
'П': win_home_reg,
'ГЗ': goals_away,
'ГП': goals_home,
})
# pd.concat склеивает две таблицы одну под другой (как два списка матчей подряд).
# ignore_index=True перенумеровывает строки заново, чтобы номера не повторялись.
both = pd.concat([home, away], ignore_index=True)
# Булевы столбцы (True/False) переводим в числа (1/0), чтобы их можно было суммировать.
flag_cols = ['В', 'ВО', 'ВБ', 'ПБ', 'ПО', 'П']
both[flag_cols] = both[flag_cols].astype(int)
# Голы тоже переводим в целые числа (после fillna(0) они стали дробными).
both[['ГЗ', 'ГП']] = both[['ГЗ', 'ГП']].astype(int)
# Суммируем всё по каждой команде.
# groupby('Команда').sum() - сгруппируй строки по названию команды и сложи числа.
table = both.groupby('Команда').sum()
# Производные показатели:
# И - количество игр = сумма всех исходов (каждый матч - ровно один из шести исходов)
table['И'] = table[flag_cols].sum(axis=1) # axis=1 - складывать вдоль строки
# О - очки по системе КХЛ: любая победа = 2, поражение в ОТ/буллитах = 1, в осн. время = 0
table['О'] = 2 * (table['В'] + table['ВО'] + table['ВБ']) + (table['ПБ'] + table['ПО'])
# Р - разница шайб (дополнительный критерий при равенстве)
table['Р'] = table['ГЗ'] - table['ГП']
'''
%О - процент набранных очков = очки / максимально возможные (2 за каждую игру).
По этому проценту КХЛ расставляет места, когда команды сыграли разное число игр.
Сайт КХЛ НЕ округляет, а отбрасывает знаки после второго (72.448 -> 72.44),
поэтому np.floor (округление вниз): умножаем на 10000, отбрасываем дробь, делим на 100.
'''
table['%О'] = np.floor(table['О'] / (2 * table['И']) * 10000) / 100
# Подписываем каждой команде её конференцию. .map(словарь) проходит по индексу
# (названиям команд) и подставляет значение из словаря.
table['Конф'] = pd.Series(table.index, index=table.index).map(conf_map)
'''
Сортировка: по проценту очков, затем при равенстве - больше побед в основное время,
затем в овертайме, затем по буллитам, затем лучше разница шайб, затем больше забито.
'''
table = table.sort_values(
by=['%О', 'В', 'ВО', 'ВБ', 'Р', 'ГЗ'],
ascending=False,
)
return table
# Вывод таблицы текстом
def show_table(table, title):
# Колонки в том же порядке и с теми же подписями, что на сайте КХЛ.
cols = ['И', 'В', 'ВО', 'ВБ', 'ПБ', 'ПО', 'П', 'ГЗ', 'ГП', 'Р', 'О', '%О']
view = table[cols].copy() # берём только нужные столбцы (копия, чтобы не трогать оригинал)
# Место по порядку строк. np.arange(1, N+1) создаёт массив [1..N].
view.insert(0, 'Место', np.arange(1, len(view) + 1))
# Имя команды (индекс таблицы) ставим отдельным столбцом, чтобы оно было видно при выводе.
view.insert(1, 'Команда', view.index)
print('\n' + '=' * 80)
print(title)
print('=' * 80)
print(view.to_string(index=False))
def main():
path = sys.argv[1] # получаем путь до csv
name = os.path.basename(path) # из khl_3/khl_*.csv берём только khl_*.csv
season = name.replace('khl_', '').replace('.csv', '')
conf_map = CONFERENCES[season]
df = pd.read_csv(path)
table = build_standings(df, conf_map)
# Итоговая таблица чемпионата
show_table(table, 'Итоговая таблица чемпионата ' + season)
# Итоговые таблицы по конференциям (фильтруем по столбцу Конф)
west = table[table['Конф'] == 'Запад']
east = table[table['Конф'] == 'Восток']
show_table(west, 'Конференция Запад ' + season)
show_table(east, 'Конференция Восток ' + season)
main()