Добавляем в Jupyter Notebooks красоту и интерактивность

Многие используют в своей работе Jupyter Notebooks. Но с ростом сложности проекта появляются проблемы. В блокноте появляются ячейки с красными пометками для самого себя «перед запуском укажи число…» или «задай количество итераций исходя из…». Какой-то откат к командной строке получается.

Да и вывод данных на экран не всегда воспринимается без пояснений сторонним человеком, который привык к красивым таблицам, картинкам и прочим современным элементам интерфейса.
Например, у нас есть данные по площадям городов и численности населения. Выведем их на экран в «традиционном виде»

tabledata = [["Москва", 2561, 12615882],
         ["Санкт-Петербург", 1439, 5383890],
         ["Ярославль", 205, 609828],
         ["Хабаровск", 383, 1321473]]
tabledata

Видим в блокноте
image

Современному, избалованному человеку такой формат отображения не всегда нравится. Нужно привести данные к более привычному табличному виду.

Можно использовать широко распространенную библиотеку pandas

import pandas as pd
pd.DataFrame(tabledata, columns=["Город","Площадь (кв. км)", "Население (человек)"])


image

Если по каким-то причинам использование pandas не устраивает, можно воспользоваться другой библиотекой или написать свою функцию.
Рассмотрим одну из таких библиотек — tabulate (https://pypi.org/project/tabulate/)
Для установки запустите из командной строки pip install tabulate

from IPython.display import HTML, display
from tabulate import tabulate 
display(HTML(tabulate(tabledata, tablefmt='html')))


image
Можно вывести данные в «псевдографическом» виде.

print(tabulate(tabledata))


7oa17avuxtjxzfwn4_jnkbycmgq.jpeg
Можно добавить заголовки

print(tabulate(tabledata, headers=["Город","Площадь (кв. км)", "Население (человек)"]))


xkg_obder3vl8jcmmnz0nlgpoji.jpeg
И индексы

display(HTML(tabulate(tabledata, headers=["Город","Площадь (кв. км)", "Население (человек)"], tablefmt='html', showindex="always")))


acbj568ccrkf1nru5x5ohuojkd0.jpeg
tabulate позволяет получить визуально такой же результат как и pandas

Можно написать свою функцию, которая потом обрастёт дополнительными возможностями.

from IPython.display import HTML, display

def dataToTable(data, columns = None):
    if len(data) == 0 :  
        display(HTML('Нет данных'))
        return
    
    hdr = ''
    if columns != None:
        for col in columns: # Формируем заголовок таблицы
            hdr = hdr + '' + col + ''
            
        hdr = '' + hdr + ''

    dt = ''
    for row in data: # Проходим циклом по всем строкам
        dt = dt + ''
        for cell in row: # И формируем тело таблицы
            dt = dt + '' + str(cell) + ''
        dt = dt + ''
            
    display(HTML('' + hdr + dt + '
')) # Выводим таблицу на экран dataToTable(tabledata, columns=["Город","Площадь (кв. км)", "Население (человек)"])


xkg_obder3vl8jcmmnz0nlgpoji.jpeg

Вывод изображений


Мы привыкли к пиктограммам и иконкам. Даже в прогнозе погоды мы видим картинки с солнышками и тучками. Чтобы добавить изображения в наши программы можно использовать библиотеку IPython. Ее функция Image позволяет работать с изображениями (PNG/JPEG/GIF), размещенными как локально, так и на интернет-ресурсах. Задавать их размеры.

Описание библиотеки здесь ipython.readthedocs.io/en/stable/api/generated/IPython.display.html? highlight=display#IPython.display.Image

from IPython.display import Image # Библиотека для отображения картинок

display(Image(url='https://habrastorage.org/webt/9m/2c/zd/9m2czdt-uv7oe6v-nws3frtw7-a.jpeg', 
              width = 200) # Задаем ширину картинки
       ) 
# display(Image(filename='Python_royal_35.JPG', width = 200)) # Локальный файл


Любуемся питоном
image

Украшаем текст


Конечно, можно генерировать HTML напрямую, используя все его возможности

from IPython.core.display import display, HTML
display(HTML("Мой зеленый текст"))


emyqqidxicsdennitipsamqpbx8.jpeg
А можно воспользоваться библиотекой termcolor. Она позмоляет не углубляясь в HTML задавать цвет текста и фона, задавать атрибуты шрифта. Описание библиотеки тут — pypi.org/project/termcolor

from termcolor import colored # Для установки запустите из командной строки pip install termcolor
print(colored("Красный текст (без атрибутов)", "red"))
print(colored("Красный текст (подчеркивание)", "red",  attrs=["underline"]))
print(colored("Зеленый текст на красном фоне (без атрибутов)", "green", "on_red" ))
print(colored("Зеленый текст на красном фоне (bold)", "green", "on_red", attrs=["bold"]))


kgkfgtasmbfutlqiuohoioftjmk.jpeg

Отображаем прогресс выполнения задачи


Никому не нравится следить за ходом выполнения длительной задачи не понимая какая часть работы уже выполнена.
pmz-1o0ehm9uvdyjbobsg2llyla.jpeg
Видеть сколько осталось — гораздо приятнее (да-да, знаю, что скорость движения «червяка» может меняться)
omeho3yd38s4_m93s76rmh0oq1s.png
Описание библиотеки тут — ipywidgets.readthedocs.io

Для установки используйте команды

pip install ipywidgets
jupyter nbextension enable --py widgetsnbextension

from ipywidgets import IntProgress
from IPython.display import display
import time

prgBar = IntProgress(min = 0, max = 100) # Создаем прогрессбар
display(prgBar) # Выводим прогрессбар на экран

while prgBar.value < prgBar.max:   # Пока положение не дошло до максимума - продолжаем цикл
    prgBar.value = prgBar.value + 1 # Двигаем "полоску"
    time.sleep(0.1)
    
print('Процесс завершен')


nj5cksfkjfd5itwjc4qyykt9f78.jpeg

Интерактивное взаимодействие с пользователем


Та же библиотека ipywidgets позволяет не только отображать, но и вносить информацию.

Самый, наверное, простой пример взаимодействия с пользователем — это реакция на нажатие кнопки. Библиотека ipywidgets позволяет создать кнопку с заданными мараметрами (текстом, стилем и размерами) и назначить функцию-обработчик ее нажатия.

from IPython.display import display
from ipywidgets import Button 

# Создаем кнопку с нужными параметрами
button1 = Button(description="Нажми меня!", 
                        button_style='success' # 'success', 'info', 'warning', 'danger', ''
                        )

def on_button_clicked(b): # Описываем обработчик события
    print("Клик")

button1.on_click(on_button_clicked) # Назначаем этот обработчик на событие "on_click"

display(button1) # Отображаем кнопку


indjxjrqvwfw1gfhmsivctd0ps8.jpeg
Размер кнопки задается при помощи свойства layout

from IPython.display import display
from ipywidgets import Button, Layout

button2 = Button(description='Кнопка с заданными размерами', button_style='success',
           layout=Layout(width='50%', height='80px'))
display(button2)


ifroduzso2pydg8exrd2sl9aruy.jpeg
Для удобного ввода пользователем чисел и дат есть компоненты FloatSlider и DatePicker.

Чтобы получить введенное значение — используется свойство <компонент>.value

Чтобы отловить момент изменения значений, нужно использовать событие observe

from IPython.display import display
from ipywidgets import FloatSlider

fSlider = FloatSlider(
    value=7.5, # Первоначальное значение
    min=0,     # Минимум
    max=10.0,  # Максимум
    step=0.1,  # Шаг изменения
    description='Параметр:',
    continuous_update=False,  # True - событие observe возникает для каждого шага при изменении значения
    orientation='horizontal'  # Горизонтальное или вертикальное расположение
)

def on_value_change(b):
    print(b['old'], '->', b['new'])

fSlider.observe(on_value_change, names='value')
display(fSlider)


6f0ycuho1upg3bis7ni2orccnbu.jpeg
Проверим доступ к текущему значению:,

fSlider.value


Интерактивный календарь:

from IPython.display import display
from ipywidgets import DatePicker

dPicker = DatePicker(
    description='Дата:'
)

def on_button_clicked(b):
    print(b['old'], '->', b['new'])

dPicker.observe(on_button_clicked, names='value')
display(dPicker)


flcd4rjdhgsamm4y7lwbuon1qnq.jpeg
Для выбора одного значения из нескольких вариантов есть список RadioButtons, выпадающий список Dropdown и группа кнопок ToggleButtons. value и observe для этих компонентов используются точно так же.

Значения можно задавать как в виде перечня строчных величин, так и в виде списка кортежей.

Попробуем самый простой вариант, со значениями в виде списка строк.

from IPython.display import display
from ipywidgets import RadioButtons

rButtons1 = RadioButtons(
    options=['Красный', 'Желтый', 'Зеленый'],
    value='Желтый', # Выбор по умолчанию
    description='Цвет:'
)


def on_button_clicked(b):
    print(b['old'], '->', b['new'])

rButtons1.observe(on_button_clicked, names='value')
display(rButtons1)

l1_ub5iax-5xfamatf6bfh3qe6y.jpegВыведем на экран значение

rButtons1.value

В этом режиме значением rButtons1.value является строка.

Пробуем второй вариант задания списка значений:

from IPython.display import display
from ipywidgets import RadioButtons

rButtons2 = RadioButtons(
    options=[('Красный', 1), ('Желтый', 2), ('Зеленый', 3)],
    value=2, # Выбор по умолчанию
    description='Цвет:' 
)

def on_button_clicked(b):
    print(b['old'], '->', b['new'])

rButtons2.observe(on_button_clicked, names='value')
display(rButtons2)

mvkgpton0y6sh4cnzo_p2dcdfcm.jpegВ этом режиме значением rButtons2.value является число, соответствующее выбранному значению.

Аналогично работает выпадающий список (Dropdown)

from IPython.display import display
from ipywidgets import Dropdown

dropdown1 = Dropdown(
    options=[('Красный', 1), ('Желтый', 2), ('Зеленый', 3)],
    value=2, # Выбор по умолчанию
    description='Цвет:' 
)

def on_button_clicked(b):
    print(b['old'], '->', b['new'])

dropdown1.observe(on_button_clicked, names='value')
display(dropdown1)

fjy-cu81pneppzaqkcumxsky79c.jpeg
Для ввода булевых значений можно использовать Checkbox и ToggleButton. У них есть уже знакомые нам value и observe.

from IPython.display import display
from ipywidgets import Checkbox

cb1 = Checkbox(
    value=False,
    description='Согласен' 
)

def on_button_clicked(b):
    print(cb1.value)

cb1.observe(on_button_clicked, names='value')
display(cb1)

fsxdvzh2pilbgroylp58voa-lk4.jpeg

from IPython.display import display
from ipywidgets import ToggleButton

tb1 = ToggleButton(
    value=False,
    description='Не нажата',
    disabled=False,
    button_style='success', # 'success', 'info', 'warning', 'danger' or ''
    tooltip='Принять условия',
    icon='check'
)
tb2 = ToggleButton(
    value=True,
    description='А эта нажата',
    disabled=False,
    button_style='success', # 'success', 'info', 'warning', 'danger' or ''
    tooltip='Принять условия',
    icon='check'
)

display(tb1, tb2)

m6hk0uunvgny-h64fxsvepkjbvg.jpegДля ввода многострочного текста служет компонент Textarea

from IPython.display import display
from ipywidgets import Textarea, Layout

Textarea1 = Textarea(
    value='Привет, Habr!',
    placeholder='Введите текст',
    description='Текст:',
    layout=Layout(width='600px', height='100px')
)

display(Textarea1)

-7qi1i4wi5slolwelmywxpyibyo.jpeg

ИИ (интерфейсные изыски)

Когда элементов интерфейса становится слишком много, хочется пойти по пути десктопных приложений и объединить отдельные элементы в обособленные группы.

Для этого нам пригодятся Accordion и Tab.

from IPython.display import display
from ipywidgets import Accordion, IntSlider, Text

accordion = Accordion(children=[IntSlider(value=42), Text(value='Сорок два')])
accordion.set_title(0, 'Раздел 1')
accordion.set_title(1, 'Раздел 2')
display(accordion)

nx7or5apcafwwecsqsztwkokuc4.jpeg

from IPython.display import display
from ipywidgets import Tab, IntSlider, Text

tab = Tab()
tab.children = [IntSlider(value=42), Text(value='Сорок два')]
tab.set_title(0, 'Раздел 1')
tab.set_title(1, 'Раздел 2')

display(tab)

xyclocdakpaa5hsr9efzthaa4pu.jpeg
Библиотека ipywidgets не ограничивается элементами, которые я перечислил. В ней еще масса полезных вещей, которые могут сделать программы в Jupyter Notebooks более привлекательными.

Смотрите тут ipywidgets.readthedocs.io/en/stable/examples/Widget%20List.html

© Habrahabr.ru