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

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

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

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

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

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

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

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

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

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



    Если по каким-то причинам использование 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')))



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

    print(tabulate(tabledata))


    Можно добавить заголовки

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


    И индексы

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


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

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

    from IPython.display import HTML, display
    
    def dataToTable(data, columns = None):
        if len(data) == 0 :  
            display(HTML('<b>Нет данных</b>'))
            return
        
        hdr = ''
        if columns != None:
            for col in columns: # Формируем заголовок таблицы
                hdr = hdr + '<th>' + col + '</th>'
                
            hdr = '<tr>' + hdr + '</tr>'
    
        dt = ''
        for row in data: # Проходим циклом по всем строкам
            dt = dt + '<tr>'
            for cell in row: # И формируем тело таблицы
                dt = dt + '<td>' + str(cell) + '</td>'
            dt = dt + '</tr>'
                
        display(HTML('<table>' + hdr + dt + '</table>')) # Выводим таблицу на экран
    
    dataToTable(tabledata, columns=["Город","Площадь (кв. км)", "Население (человек)"])



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


    Мы привыкли к пиктограммам и иконкам. Даже в прогнозе погоды мы видим картинки с солнышками и тучками. Чтобы добавить изображения в наши программы можно использовать библиотеку 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("<font color='green'>Мой зеленый текст</font>"))



    А можно воспользоваться библиотекой 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"]))



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


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



    Видеть сколько осталось — гораздо приятнее (да-да, знаю, что скорость движения «червяка» может меняться).



    Описание библиотеки тут — 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('Процесс завершен')



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


    Та же библиотека 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) # Отображаем кнопку



    Размер кнопки задается при помощи свойства 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)



    Для удобного ввода пользователем чисел и дат есть компоненты 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)



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

    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)



    Для выбора одного значения из нескольких вариантов есть список 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)



    Выведем на экран значение:

    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)



    В этом режиме значением 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)



    Для ввода булевых значений можно использовать 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)



    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)



    Для ввода многострочного текста служет компонент 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)



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


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

    Для этого нам пригодятся 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)



    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)



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

    Смотрите тут
    • +18
    • 6,3k
    • 9
    Поддержать автора
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 9

      +2
      Что посоветуете если проект разрастается, и перестает иметь смысл держать весь код в одном файле?
        0
        Я выношу части в отдельные файлы и подключаю их через import в блокноте. Лучшего не нашел.

        0

        Я выношу части в отдельные файлы и подключаю их через import в блокноте. Лучшего не нашел.

          0

          А с voila пробовали работать?

            0
            Не пробовал, посмотрел по вашей наводке, понравились.
            +3
            Для индикации прогресса еще классная библиотечка tqdm (не реклама, не моя!). Заворачиваете то, по чему итерируете, в tqdm или tqdm_notebook, и оно создает прогресс бар для консоли и/или для jupyter.

            Показывает текстовый прогрессбар:
            for i in tqdm(range(10000)):
               run_something()
            
            100%|██████████| 100/100 [00:00<00:00, 178633.05it/s]
            


            Или для красивого графического прогрессбара:
            for i in tqdm_notebook(range(10000)):
               run_something()
            
              +1

              tqdm это вообще одна из лучших библиотек в питоне по моему мнению — делает ровно то, что заявлено, пользоваться элементарно.

              0

              Очень зашёл streamlit — написал полноценный прототипчик на нем, идеальный UX от использования!

                0
                Да, его пробовал. Понравился

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое