Привет, Хабр!
Прошло чуть больше недели с окончания Чемпионата Европы 2016 во Франции. Этот чемпионат запомнится нам неудачным выступлением сборной России, проявленной волей сборной Исландии, потрясающей игрой сборных Франции и Португалии. В этой статье мы поработаем с данными, построим несколько графиков и отредактируем их в векторном редакторе Inkscape. Кому интересно — прошу под кат.
Содержание
- Работа с данными API
- Визуализация данных с помощью Python
- Редактирование векторной графики в Inkscape
1. Работа с данными API
Чтобы визуализировать футбольные данные сначала их необходимо получить. Для этих целей мы будем использовать API football-data.org. Нам доступны следующие методы:
- Список соревнований — «api.football-data.org/v1/competitions/?season={year}»
- Список команд (по идентификатору соревнования) — «api.football-data.org/v1/competitions/{competition_id}/teams»
- Список матчей (по идентификатору соревнования) — «api.football-data.org/v1/competitions/{competition_id}/fixtures»
- Информация о команде — «api.football-data.org/v1/teams/{team_id}»
- Список футболистов (по идентификатору команды) — «api.football-data.org/v1/teams/{team_id}/players»
Подробная информация о всех методах находится в документации API.
Давайте теперь попробуем поработать с данными. Для начала попробуем изучить структуру возвращаемых данных для метода «competitions» (список соревнований):
[{'_links': {'fixtures': {'href': 'http://api.football-data.org/v1/competitions/424/fixtures'},
'leagueTable': {'href': 'http://api.football-data.org/v1/competitions/424/leagueTable'},
'self': {'href': 'http://api.football-data.org/v1/competitions/424'},
'teams': {'href': 'http://api.football-data.org/v1/competitions/424/teams'}},
'caption': 'European Championships France 2016',
'currentMatchday': 7,
'id': 424,
'lastUpdated': '2016-07-10T21:32:20Z',
'league': 'EC',
'numberOfGames': 51,
'numberOfMatchdays': 7,
'numberOfTeams': 24,
'year': '2016'},
{'_links': {'fixtures': {'href': 'http://api.football-data.org/v1/competitions/426/fixtures'},
'leagueTable': {'href': 'http://api.football-data.org/v1/competitions/426/leagueTable'},
'self': {'href': 'http://api.football-data.org/v1/competitions/426'},
'teams': {'href': 'http://api.football-data.org/v1/competitions/426/teams'}},
'caption': 'Premiere League 2016/17',
'currentMatchday': 1,
'id': 426,
'lastUpdated': '2016-06-23T10:42:02Z',
'league': 'PL',
'numberOfGames': 380,
'numberOfMatchdays': 38,
'numberOfTeams': 20,
'year': '2016'},
Прежде чем начать работать с Python, нам понадобится сделать необходимые импорты:
import datetime
import random
import re
import os
import requests
import json
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
pd.set_option('display.width', 1100)
plt.style.use('bmh')
DIR = os.path.dirname('__File__')
font = {'family': 'Verdana', 'weight': 'normal'}
rc('font', **font)
Структура данных понятна, попробуем загрузить данные о соревнованиях за 2015-2016 год в pandas.DataFrame. Параллельно будем сохранять данные, с которыми будем работать в .csv файлы, поскольку бесплатно можно делать лишь 50 запросов к API в день. Ограничения также накладываются и на временной период данных — бесплатно нам доступны лишь 2015-2016 годы.
competitions_url = 'http://api.football-data.org/v1/competitions/?season={year}'
data = []
for y in [2015, 2016]:
response = requests.get(competitions_url.format(year=y))
competitions = json.loads(response.text)
competitions = [{'caption': c['caption'], 'id': c['id'], 'league': c['league'], 'year': c['year'],
'games_count': c['numberOfGames'], 'teams_count': c['numberOfTeams']} for c in competitions]
COMP = pd.DataFrame(competitions)
data.append(COMP)
COMP = pd.concat(data)
COMP.to_csv(os.path.join(DIR, 'input', 'competitions_2015_2016.csv'))
COMP.head(20)
Отлично! Мы видим чемпионат Европы 2016 в списке:
caption | games_count | id | league | teams_count | year | |
---|---|---|---|---|---|---|
12 | League One 2015/16 | 552 | 425 | EL1 | 24 | 2015 |
0 | European Championships France 2016 | 51 | 424 | EC | 24 | 2016 |
Теперь мы можем получить список команд по идентификатору (id) этих соревнований — используем метод teams для получения списка команд Евро-2016.
teams_url = 'http://api.football-data.org/v1/competitions/{competition_id}/teams'
response = requests.get(teams_url.format(competition_id=424))
teams = json.loads(response.text)['teams']
teams = [dict(code=team['code'], name=team['name'], flag_url=team['crestUrl'], players_url=team['_links']['players']['href'])
for team in teams]
TEAMS = pd.DataFrame(teams)
TEAMS.to_csv(os.path.join(DIR, 'input', 'teams_euro2016.cvs'))
TEAMS.head(24)
code | flag_url | name | players_url | |
---|---|---|---|---|
0 | FRA | upload.wikimedia.org/wikipedia/en/c/c3... | France | api.football-data.org/v1/teams/773/players |
1 | ROU | upload.wikimedia.org/wikipedia/commons... | Romania | api.football-data.org/v1/teams/811/players |
2 | ALB | upload.wikimedia.org/wikipedia/commons... | Albania | api.football-data.org/v1/teams/1065/pla... |
3 | SUI | upload.wikimedia.org/wikipedia/commons... | Switzerland | api.football-data.org/v1/teams/788/players |
4 | WAL | upload.wikimedia.org/wikipedia/commons... | Wales | api.football-data.org/v1/teams/833/players |
Структура нашей таблицы таблица (DataFrame) представлена со следующими столбцами:
- code (трехзначный код страны)
- flag_url (ссылка на .svg файл флага страны в нашем случае, а в оригинале crestUrl — .svg файл символа команды)
- name (наименование команды)
- players_url (ссылка на страницу API с данными об игроках — почему-то данных об игроках сборных нет, возможно, это еще одно ограничение API)
В нашей последующей работе нам также понадобятся .svg файлы флагов команд — просто скачаем их в отдельную директорию, я сделал это быстро и непринужденно с помощью расширения Chrono для Chrome.
Теперь попробуем получить данные непосредственно о матчах Евро-2016. Сначала вновь посмотрим структуру ответа сервера для выбранного метода «competitions». Для примера разберем структуру информации о матче, закончившемся серией пенальти (это максимально сложный состав данных из возможных для этого метода).
games_url = 'http://api.football-data.org/v1/competitions/{competition_id}/fixtures'
response = requests.get(games_url.format(competition_id=424))
games = json.loads(response.text)['fixtures']
# Для примера разберем структуру информации о матче, закончившемся серией пенальти.
games_selected = [game for game in games if 'extraTime' in game['result']]
games_selected[0]
В ответе получаем следующее:
{'_links': {'awayTeam': {'href': 'http://api.football-data.org/v1/teams/794'},
'competition': {'href': 'http://api.football-data.org/v1/competitions/424'},
'homeTeam': {'href': 'http://api.football-data.org/v1/teams/788'},
'self': {'href': 'http://api.football-data.org/v1/fixtures/150457'}},
'awayTeamName': 'Poland',
'date': '2016-06-25T13:00:00Z',
'homeTeamName': 'Switzerland',
'matchday': 4,
'result': {'extraTime': {'goalsAwayTeam': 1, 'goalsHomeTeam': 1},
'goalsAwayTeam': 1,
'goalsHomeTeam': 1,
'halfTime': {'goalsAwayTeam': 1, 'goalsHomeTeam': 0},
'penaltyShootout': {'goalsAwayTeam': 5, 'goalsHomeTeam': 4}},
'status': 'FINISHED'}
Для облегчения работы с данными и процесса загрузки их в DataFrame напишем небольшую функцию, которая в качестве атрибута принимает словарь с информацией о матче и возвращает словарь удобного для нас вида.
def handle_game(game):
date = game['date']
team_1 = game['homeTeamName']
team_2 = game['awayTeamName']
matchday = game['matchday']
team_1_goals_main = game['result']['goalsHomeTeam']
team_2_goals_main = game['result']['goalsAwayTeam']
status = game['status']
if 'extraTime' in game['result']:
team_1_goals_extra = game['result']['extraTime']['goalsHomeTeam']
team_2_goals_extra = game['result']['extraTime']['goalsAwayTeam']
if 'penaltyShootout' in game['result']:
team_1_goals_penalty = game['result']['penaltyShootout']['goalsHomeTeam']
team_2_goals_penalty = game['result']['penaltyShootout']['goalsAwayTeam']
else:
team_1_goals_penalty = team_2_goals_penalty = 0
else:
team_1_goals_extra = team_2_goals_extra = team_1_goals_penalty = team_2_goals_penalty = 0
team_1_goals = team_1_goals_main + team_1_goals_extra
team_2_goals = team_2_goals_main + team_2_goals_extra
if (team_1_goals + team_1_goals_penalty) > (team_2_goals + team_2_goals_penalty):
team_1_win = 1
team_2_win = 0
draw = 0
elif (team_1_goals + team_1_goals_penalty) < (team_2_goals + team_2_goals_penalty):
team_1_win = 0
team_2_win = 1
draw = 0
else:
team_1_win = team_2_win = 0
draw = 1
game = dict(date=date, team_1=team_1, team_2=team_2, matchday=matchday, status=status,
team_1_goals=team_1_goals, team_2_goals=team_2_goals,
team_1_goals_extra=team_1_goals_extra, team_2_goals_extra=team_2_goals_extra,
team_1_win=team_1_win, team_2_win=team_2_win, draw=draw,
team_1_goals_penalty=team_1_goals_penalty, team_2_goals_penalty=team_2_goals_penalty)
return game
# Сразу попробуем использовать функцию на примере нашей записи с пенальти
game = handle_game(games_selected[0])
print(game)
Вот как выглядит возвращаемый словарь:
{'date': '2016-06-25T13:00:00Z',
'draw': 0,
'matchday': 4,
'status': 'FINISHED',
'team_1': 'Switzerland',
'team_1_goals': 2,
'team_1_goals_extra': 1,
'team_1_goals_penalty': 4,
'team_1_win': 0,
'team_2': 'Poland',
'team_2_goals': 2,
'team_2_goals_extra': 1,
'team_2_goals_penalty': 5,
'team_2_win': 1}
Теперь у нас всё готово для загрузки данных обо всех матчах Евро-2016 в DataFrame.
games_url = 'http://api.football-data.org/v1/competitions/{competition_id}/fixtures'
response = requests.get(games_url.format(competition_id=424))
games = json.loads(response.text)['fixtures']
GAMES = pd.DataFrame([handle_game(g) for g in games])
GAMES.to_csv(os.path.join(DIR, 'input', 'games_euro2016.csv'))
GAMES.head()
date | draw | matchday | status | team_1 | team_1_goals | team_1_goals_extra | team_1_goals_penalty | team_1_win | team_2 | team_2_goals | team_2_goals_extra | team_2_goals_penalty | team_2_win | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2016-06-10T19:00:00Z | 0 | 1 | FINISHED | France | 2 | 0 | 0 | 1 | Romania | 1 | 0 | 0 | 0 |
1 | 2016-06-11T13:00:00Z | 0 | 1 | FINISHED | Albania | 0 | 0 | 0 | 0 | Switzerland | 1 | 0 | 0 | 1 |
Отлично, данные у нас загружены в DataFrame, но такая структура не подходит для анализа данных. Руководствуясь принципами документа «Tidy Data, Hadley Wickham (2014)», скорректируем структуру DataFrame таким образом, чтобы одна переменная (команда) была тождественна одной строке — фактически кол-во строк Dataframe мы увеличим вдвое. Также скорректируем названия столбцов, чтобы с ними было проще работать.
# Помимо всего прочего приведем столбец даты к формату даты.
GAMES['date'] = pd.to_datetime(GAMES.date)
# Для начала расположим столбцы в удобном порядке и скорректируем их названия (сократим)
GAMES = GAMES.reindex(columns=['date', 'matchday', 'status',
'team_1', 'team_1_win', 'team_1_goals', 'team_1_goals_extra', 'team_1_goals_penalty',
'team_2', 'team_2_win', 'team_2_goals', 'team_2_goals_extra', 'team_2_goals_penalty',
'draw'])
new_columns = ['date', 'mday', 'status', 't1', 't1w', 't1g', 't1ge', 't1gp', 't2', 't2w', 't2g', 't2ge', 't2gp', 'draw']
GAMES.columns = new_columns
GAMES.head()
# Теперь создадим итоговый DataFrame
# Создадим копию DataFrame с играми, перетасовав столбцы, и объединим его с оригинальным DataFrame.
GAMES_2 = GAMES.ix[:,[0, 1, 2, 8, 9, 10, 11, 12, 3, 4, 5, 6, 7, 13]]
GAMES_2.columns = new_columns
GAMES_F = pd.concat([GAMES, GAMES_2])
GAMES_F.sort(['date'], inplace=True)
GAMES_F.head()
date | mday | status | t1 | t1w | t1g | t1ge | t1gp | t2 | t2w | t2g | t2ge | t2gp | draw | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2016-06-10 19:00:00 | 1 | FINISHED | France | 1 | 2 | 0 | 0 | Romania | 0 | 1 | 0 | 0 | 0 |
0 | 2016-06-10 19:00:00 | 1 | FINISHED | Romania | 0 | 1 | 0 | 0 | France | 1 | 2 | 0 | 0 | 0 |
1 | 2016-06-11 13:00:00 | 1 | FINISHED | Albania | 0 | 0 | 0 | 0 | Switzerland | 1 | 1 | 0 | 0 | 0 |
Такая структура куда лучше, но для дальнейшей работы нам понадобится еще несколько небольших изменений.
# Для удобства отображения информации в таблице добавим еще один столбец - "g" - общее кол-во забитых за матч голов
GAMES_F['g'] = GAMES_F.t1g + GAMES_F.t2g
GAMES_F['idx'] = GAMES_F.index
# Для удобства отображения некоторых графиков добавим еще данные о стадии того или иного матча.
# Мы знаем, что всего матчей было 51, из них 15 матчей плей-офф, остальные - групповой этап.
# Давайте добавим эту информацию в DataFrame
TP = pd.DataFrame({'typ': ['Группы']*36 + ['1/8']*8 + ['1/4']*4 + ['1/2']*2 + ['Финал']*1,
'idx': range(0, 51)})
# Сформируем итоговый DataFrame
GAMES_F= pd.merge(GAMES_F, TP, how='left', left_on=GAMES_F.idx, right_on=TP.idx)
GAMES_F.head()
date | mday | status | t1 | t1w | t1g | t1ge | t1gp | t2 | t2w | t2g | t2ge | t2gp | draw | g | idx_x | idx_y | typ | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2016-06-10 19:00:00 | 1 | FINISHED | France | 1 | 2 | 0 | 0 | Romania | 0 | 1 | 0 | 0 | 0 | 3 | 0 | 0 | Группы |
1 | 2016-06-10 19:00:00 | 1 | FINISHED | Romania | 0 | 1 | 0 | 0 | France | 1 | 2 | 0 | 0 | 0 | 3 | 0 | 0 | Группы |
2 | 2016-06-11 13:00:00 | 1 | FINISHED | Albania | 0 | 0 | 0 | 0 | Switzerland | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | Группы |
2. Визуализация данных с помощью Python
Для визуализации данных мы будем использовать библиотеку matplotlib. На этом этапе мы фактически подготовим графики в формате .svg для их дальнейшей обработки в векторном редакторе.
Для начала подготовим график-таймлайн всех матчей Евро-2016. Мне показалось, что он будет достойно и удобно смотреться в форме горизонтальных столбцов.
GAMES_W = GAMES_F[(GAMES_F.t1w==1) | (GAMES_F.draw==1)]
GAMES_W['dt'] =[d.strftime('%d.%m.%Y') for d in GAMES_W.date]
# Отформатируем подписи для осей
GAMES_W['l1'] = (GAMES_W.idx_x + 1).astype(str) + ' - ' + GAMES_W.dt + ' ' + (GAMES_W.typ)
GAMES_W['l2'] = GAMES_W.t2 + ' ' + GAMES_W.t2g.astype(str) + ':' + GAMES_W.t1g.astype(str) + ' ' + GAMES_W.t1
fig, ax1 = plt.subplots(figsize=[10, 30])
ax1.barh(GAMES_W.idx_x, GAMES_W.t1g, color='#01aae8')
ax1.set_yticks(GAMES_W.idx_x + 0.5)
ax1.set_yticklabels(GAMES_W.l1.values)
ax2 = ax1.twinx()
ax2.barh(GAMES_W.idx_x, -GAMES_W.t2g, color='#f79744')
ax2.set_yticks(GAMES_W.idx_x + 0.5)
ax2.set_yticklabels(GAMES_W.l2.values)
# Хорошо. Теперь мы нанесли почти достаточно информации для редактирования в редакторе - нам осталось лишь
# нанести информацию для матчей, закончившихся серией пенальти.
ax3 = ax1.twinx()
ax3.barh(GAMES_W.idx_x, GAMES_W.t2gp, 0.5, alpha=0.2, color='blue')
ax3.barh(GAMES_W.idx_x, -GAMES_W.t1gp, 0.5, alpha=0.2, color='orange')
ax1.grid(False)
ax2.grid(False)
ax1.set_xlim(-6, 6)
ax2.set_xlim(-6, 6)
# Теперь можно сохранить в файл для обработки
plt.show()
fig.savefig(os.path.join(DIR, 'output', 'barh_1.svg'))
На выходе мы получим вот такой неказистый график-таймлайн.
Давайте теперь узнаем, какая же сборная забила больше всего мячей. Для этого создадим группированный DataFrame из существующего и расcчитаем некоторые агрегированные показатели.
GAMES_GR = GAMES_F.groupby(['t1'])
GAMES_AGG = GAMES_GR.agg({'t1g': np.sum})
GAMES_AGG.sort(['t1g'], ascending=False).head()
teams goals
France 13
Wales 10
Portugal 10
Belgium 9
Iceland 8
Интересно, но давайте подсчитаем еще несколько показателей:
- Кол-во сыгранных матчей
- Кол-во побед/ничьих/поражений
- Кол-во забитых/пропущенных голов
- Кол-во матчей с дополнительным временем
- Кол-во матчей, закончившехся серией пенальти
GAMES_F['n'] = 1
GAMES_GR = GAMES_F.groupby(['t1'])
GAMES_AGG = GAMES_GR.agg({'n': np.sum, 'draw': np.sum, 't1w': np.sum, 't2w':np.sum,
't1g': np.sum, 't2g': np.sum})
GAMES_AGG['games_extra'] = GAMES_GR.apply(lambda x: x[x['t1ge']>0]['n'].count())
GAMES_AGG['games_penalty'] = GAMES_GR.apply(lambda x: x[x['t1gp']>0]['n'].count())
GAMES_AGG.reset_index(inplace=True)
GAMES_AGG.columns = ['team', 'draw', 'goals_lose', 'lose', 'win', 'goals',
'games', 'games_extra', 'games_penalty']
GAMES_AGG = GAMES_AGG.reindex(columns = ['team', 'games', 'games_extra', 'games_penalty',
'win', 'lose', 'draw', 'goals', 'goals_lose'])
Теперь визуализируем эти распределения:
GAMES_P = GAMES_AGG
GAMES_P.sort(['goals'], ascending=False, inplace=True)
GAMES_P.reset_index(inplace=True)
bar_width = 0.6
fig, ax = plt.subplots(figsize=[16, 6])
ax.bar(GAMES_P.index + 0.2, GAMES_P.goals, bar_width, color='#01aae8', label='Забито')
ax.bar(GAMES_P.index + 0.2, -GAMES_P.goals_lose, bar_width, color='#f79744', label='Пропущено')
ax.set_xticks(GAMES_P.index)
ax.set_xticklabels(GAMES_P.team, rotation=45)
ax.set_ylabel('Голы')
ax.set_xlabel('Команды')
ax.set_title('Топ команд по кол-ву забитых мячей')
ax.legend()
plt.grid(False)
plt.show()
fig.savefig(os.path.join(DIR, 'output', 'bar_1.svg'))
На выходе мы получаем такой график:
Нарисуем еще один график распределения побед, поражений и ничьих на Евро.
GAMES_P = GAMES_AGG
GAMES_P.sort(['win'], ascending=False, inplace=True)
GAMES_P.reset_index(inplace=True)
bar_width = 0.6
fig, ax = plt.subplots(figsize=[16, 6])
ax.bar(GAMES_P.index + 0.2, GAMES_P.win, bar_width, color='#01aae8', label='Победы')
ax.bar(GAMES_P.index + 0.2, -GAMES_P.lose, bar_width/2, color='#f79744', label='Поражения')
ax.bar(GAMES_P.index + 0.5, -GAMES_P.draw, bar_width/2, color='#99b286', label='Ничьи')
ax.set_xticks(GAMES_P.index)
ax.set_xticklabels(GAMES_P.team, rotation=45)
ax.set_ylabel('Матчи')
ax.set_xlabel('Команда')
ax.set_title('Топ команд по кол-ву побед')
ax.set_ylim(-GAMES_P.lose.max()*1.2, GAMES_P.win.max()*1.2)
ax.legend(ncol=3)
plt.grid(False)
plt.show()
fig.savefig(os.path.join(DIR, 'output', 'bar_2.svg'))
На выходе мы получаем такой график:
Пользуясь случаем, хочется отметить, что лидер по количеству забитых мячей и количеству побед Франция не стал победителем чемпионата, а Португалия, с 3 матчами в ничью, меньшим количеством забитых мячей и большим, чем у Франции количеством пропущенных, в итоге получила кубок.
3. Редактирование векторной графики в Inkscape
Теперь давайте отредактируем получившиеся у нас графики в векторном редакторе Inkscape. Выбор пал на этот редактор по одной причине — он бесплатный. Сразу хотелось бы отметить, что к ресурсам системы он также крайне требователен — опытным путём было определенно, что минимальное кол-во оперативной памяти для работы — 8гб. У меня было 4гб, так что к концу правки одной из иллюстраций я уже начинал нервничать от длительных ожиданий. Для комфортной работы в редакторе рекомендуется ознакомиться с вводными туториалами на сайте.
Основной задачей редактирования графиков в векторном редакторе является возможность сделать красивые визуализации с помощью встроенных функций редактора. При этом мы можем:
- Выбирать, изменять и перемещать любой элемент графика, созданный с помощью python
- Как угодно дополнять отрисованные данные из внешних источников, например — вставлять флаги стран
- Экспортировать данные в каком угодно разрешении в большинство графических форматов
Я не буду длительно описывать преобразования, которые я сделал в Inkscape, а просто перечислю их и представлю итоговые получившиеся графики. Изменения:
- Отредактированы подписи на осях
- Убраны лишние заливки
- Добавлены флаги стран для более понятной визуализации
- Произведены цветовые выделения
- Добавлена символика Евро-2016
- Добавлены футбольные атрибуты (например, мячи, отражающие количество забитых мячей на таймлайне)
Вот что получилось в итоге:
Спасибо за внимание.
Комментарии (16)
MAXHO
20.07.2016 11:31Показательные диаграммы.
Но самое главное спасибо за код. и подробный разбор создания таких красивых диаграммmoldabekov
20.07.2016 15:00+1Еще можно вместе matplot использовать pygal (http://www.pygal.org/en/stable/). Очень красивые графики рисует. Использую для отображение статистики одного сервиса в боте Telegram.
xfishbonex
20.07.2016 13:30+1Красивые диаграммы. На примере Франции-Португалии видно, что Франция сильнее, как команда, но не видно, что Португалия при этом чемпион.
Что если добавить условную меру веса каждого матча?ikashnitsky
20.07.2016 21:40Мерой веса как раз и может выступать букмекерская ставка. А еще коэффициенты пропорциональные стадиям турнира: 1/16, 1/8, 1/4, 1/2, 1 — за финал.
MAGistr_MTM
20.07.2016 13:30Забитые и пропущенные голы лучше показывать относительно сыгранных матчей. А количество побед/ничьих/поражений — в процентном соотношении к общему количеству игр.
ikashnitsky
20.07.2016 13:33Inkscape — явно лишнее звено. Не вполне уверен насчет python и, видимо, seaborn, в R и ggplot2 нашлепнуть флаги команд на графики довольно просто. И, главное, размещать их не придется вручную — достаточно указать отступ от значения столбика. Если еще придираться, надо бы обвечти флаги рамкой — посмотрите на Англию, Польшу и Россию.
Идея анализа ставок букмекеров мне качется очень плодотворной
tibhar940
20.07.2016 13:40Я не говорил, что невозможно добавить графики без Inkscape, я просто показал, что можно подготовить графики в Python, а затем работать с векторной графикой в Inkscape. К сожалению, я не могу показать более продвинутые приёмы работы с векторным редактором, но возможности визуализации в нём для уже подготовленных графиков поистине огромные. Опять же с точки зрения удобства — можно очень быстро подготовить график, сохранить его в .svg и сделать несколько подписей в векторе для какого-либо отчета, либо долго и мучительно добавлять эти подписи в Python. А «нашлепать графики» в Python можно как с помощью библиотек визуализации, так и с помощью библиотек по работе с графикой.
Идея анализа ставок букмекеров также заинтересовала.ikashnitsky
20.07.2016 14:19+1Все так. Просто очень уж выпукло Inkscape смотрится в заголовке.
Программное решение, как правило, лучше тем, что, написанное раз, может быть без лишних усилий использовано много раз. Представьте, что Вы бы построили не 2 графика, а 25. Я бы просто написал функцию, которая прилепляет флаг страны в необходимое место.tibhar940
20.07.2016 14:31Я это прекрасно понимаю — и для постоянно подготавливаемых отчетов сделал бы также. И в данном частном случае это тоже подошло бы. Но если, допустим, ежедневно над разными элементами придется делать различные подписи, различные иллюстрации добавлять, и это никак определенными правилами (критериями) не вывести в закономерность — здесь придется использовать Inkscape. Или же в случае, если задача не сводится к copy-paste файла .svg в заданную (x,y) координату другого файла .svg, а заключается в преобразовании/художественном осмыслении, использовании различных фильтров/градиентов и т.п.
P.S. — кстати, ваша статья про хоккеистов просто шикарна.ikashnitsky
20.07.2016 21:43Мне как раз очень понравилось, как вы описали выгрузку данных через API — когда вручную копировал данные хоккеистов, сожалел, что не знаю, как автоматизировать процесс. А вот с самими данными, по-моему, можно было еще много чего интересного нарисовать.
tibhar940
20.07.2016 22:03Спасибо, я рад, что понравилось. С этими данными действительно еще можно много чего придумать. Хочу приобрести платную подписку на этот API или найти другой источник данных, чтобы посмотреть общую статистику чемпионатов Европы допустим. Еще мои друзья-рецензенты высказывали несколько заинтересованностей — например, соотношение голов в первом/втором тайме, распределение голов по минутам — но поминутных данных о голах я не нашел.
fivelife
Прикольно. Можно еще добавить ставки букмекеров и оценить эффективность коэффициентов.
tibhar940
В планах есть получение полного доступа к API и анализ ставок букмекеров на более глобальном уровне.