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()