[Из песочницы] График счастья с python, pandas и matplotlib
Зима — это по истине прекрасное время года. Но именно зимой я всегда задумываюсь о том, что встаю и ухожу на работу, а затем и возвращаюсь с работы, не видя солнечного света. Сегодня мне захотелось визуализировать данные о восходе и заходе солнца и соотнести их со столь привычным для многих распорядком дня (рабочие часы и время бодрствования). Для работы мы будем использовать Python (pandas + matplotlib). Посмотрим, что из этого получилось.
Для начала нам понадобятся данные, которые можно визуализировать. Я нашел подходящий набор здесь. На странице мы видим две таблицы, содержащие данные о дате, восходе и заходе солнца, зените и продолжительности светового дня, а также данные о гражданских, навигационных и астрономических сумерках. Для работы нам понадобится время восхода и захода солнца, информация о гражданских сумерках, и, само собой дата, которую мы сможем представить на таймлайне.
Для удобства создадим папку проекта /sumerki, внутри неё создадим папку /input и скрипт приложения sumerki.py. В папку input мы положим 2 файла sumerki_1.txt и sumerki_2.txt, куда просто скопируем таблицы с сайта. Первые строки таблиц выглядят так:
1 января 09:00:39 12:33:54 16:07:09 07:06:29 +1:16 08:14:08 07:25:39 06:40:27 18:27:20 17:42:09 16:53:40
Данных из внешних источников нам вполне достаточно, теперь осталось обозначить время бодрствования и рабочее время. Не мудрствуя лукаво я решил взять следующие временные интервалы: 07:00:00 — 20:00:00 для бодрствования и 09:00:00 — 18:00:00 рабочий день.
Со структурой всё понятно. Давайте теперь немного кода на Python.
Сначала сделаем все необходимые импорты и осуществим небольшие настройки.
import os
import datetime
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib import rc
# Создадим небольшую служебную функцию для преобразования строкового времени с число
def stn(dstr):
return mdates.datestr2num(dstr.tolist())
# Простой рецепт для решения проблемы отображения кириллицы в matplotlib
font = {'family': 'Verdana', 'weight': 'normal'}
rc('font', **font)
DIR = os.path.dirname('__File__')
Сформируем данные в pandas (на выходе получим 2 датафрейма, с которыми будем работать — S, W):
# Считаем данные из текстовых файлов и сохраним их в датафрейм с заранее известными столбцами
s1 = open(os.path.join(DIR, 'input', 'sumerki_1.txt'), 'r').read().split('\n')
s2 = open(os.path.join(DIR, 'input', 'sumerki_2.txt'), 'r').read().split('\n')
oday = datetime.datetime.strptime('01.01.2016', '%d.%m.%Y')
dates = [oday + datetime.timedelta(days=dt) for dt in range(len(s1))]
# От себя мы добавим столбцы с временем начала и окончания дня (0, 24)
s = [[dates[i[0]]] + s1[i[0]].split('\t') + s2[i[0]].split('\t') + ['00:00:01', '23:59:59'] for i in enumerate(s1)]
columns = ['datetime', 'date', 'voshod', 'zenit', 'zahod', 'dolgota', 'cng',
'sum1_from', 'sum2_from', 'sum3_from', 'sum3_to', 'sum2_to', 'sum1_to',
'0', '24']
S = pd.DataFrame(s, columns=columns)
# Сформироуем датафрейм, содержащий данные о дне и начале/конце рабочего дня (бодрствования)
w = [[dt, '07:00:00', '09:00:00', '18:00:00', '20:00:00'] for dt in dates]
columns = ['datetime', 'life_from', 'work_from', 'work_to', 'life_to']
W = pd.DataFrame(w, columns=columns)
Теперь нам остаётся только отобразить все полученные данные на графике. Для удобства, я постарался подобрать цвета — желтый для светового дня, оранжевый для сумерек, а голубоватый со звёздами для темного времени суток.
# Ну а теперь построим график
fig, ax = plt.subplots()
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
plt.gca().xaxis.set_major_locator(mdates.MonthLocator())
plt.gcf().autofmt_xdate()
# Отобразим нужные данные на графике
l1, = ax.plot(S['datetime'], stn(S['voshod']), 'r-', label='восход')
l2, = ax.plot(S['datetime'], stn(S['zahod']), 'b-', label='заход')
l3, = ax.plot(S['datetime'], stn(S['sum1_from']), 'g-', label='сумерки (от)')
l4, = ax.plot(S['datetime'], stn(S['sum1_to']), 'm-', label='сумерки (до)')
l5, = ax.plot(W['datetime'], stn(W['work_from']), 'k-', label='рабочее время')
l6, = ax.plot(S['datetime'], stn(W['work_to']), 'k-', label='рабочее время')
l7, = ax.plot(S['datetime'], stn(W['life_from']), 'k-', label='время бодрствования')
l8, = ax.plot(S['datetime'], stn(W['life_to']), 'k-', label='время бодрствования')
# Заполним пространство между
# Восход-заход
plt.fill_between(S['datetime'].tolist(), stn(S['voshod']), stn(S['zahod']), alpha=0.4, color='yellow', hatch='.')
# Cумерки
plt.fill_between(S['datetime'].tolist(), stn(S['sum1_from']), stn(S['voshod']), alpha=0.4, color='orange', hatch='.')
plt.fill_between(S['datetime'].tolist(), stn(S['zahod']), stn(S['sum1_to']), alpha=0.4, color='orange', hatch='.')
# Ночь
plt.fill_between(S['datetime'].tolist(), stn(S['0']), stn(S['sum1_from']), alpha=0.4, color='blue', hatch='*')
plt.fill_between(S['datetime'].tolist(), stn(S['sum1_to']), stn(S['24']), alpha=0.4, color='blue', hatch='*')
# Рабочее время и время бодрствования
plt.fill_between(W['datetime'].tolist(), stn(W['work_from']), stn(W['work_to']), alpha=0.1, color='blue', hatch='/')
plt.fill_between(W['datetime'].tolist(), stn(W['life_from']), stn(W['life_to']), alpha=0.1, color='blue', hatch='/')
# Форматирование, заголовок и легенда
ax.yaxis_date()
ax.xaxis_date()
ax.set_xlabel("Дата")
ax.set_ylabel("Время")
plt.title('График зависимости рабочего времени (времени бодрствования) от светового дня.')
plt.legend(handles=[l1, l2, l3, l4, l6, l8], loc=1, fontsize=11)
fig.autofmt_xdate()
# Ну и покажем график уже наконец
plt.show()
Спасибо за внимание!