Книга: FastAPI: веб-разработка на Python
Назад: Глава 16. Формы и шаблоны
Дальше: Глава 18. Игры

Глава 17. Обнаружение и визуализация данных

Обзор

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

Python и данные

В последние несколько лет Python стал очень популярным по многим причинам:

• он легок в обучении;

• у него прозрачный синтаксис;

• он имеет богатую стандартную библиотеку;

• в нем огромное количество высококачественных пакетов сторонних разработчиков;

• особое внимание в нем уделяется манипулированию данными, преобразованию и самостоятельной проверке.

Последний пункт всегда был актуален для традиционных ETL-задач по созданию баз данных. Некоммерческая группа PyData (https://pydata.org) даже организует конференции и разрабатывает инструменты для анализа данных с открытым исходным кодом на Python. Популярность Python отражают также недавний всплеск развития искусственного интеллекта и потребность в инструментах для подготовки данных, используемых в моделях ИИ.

В этой главе мы попробуем применить несколько пакетов данных Python и посмотрим, как они связаны с современной веб-разработкой на Python и FastAPI.

Текстовый вывод с помощью PSV

В этом разделе мы будем использовать существ, перечисленных в приложении Б. Данные находятся в репозитории GitHub этой книги, в файле cryptid.psv с разделителем в виде вертикальной черты и в базе данных SQLite cryptid.db. Файлы, где используется разделение запятыми (.csv) и табуляцией (.tsv), широко распространены, но запятые используются в самих ячейках данных, а табуляцию иногда трудно отличить от других пробельных символов. Символ вертикальной черты (|) отличается от прочих и достаточно редко встречается в стандартном тексте, чтобы служить хорошим разделителем.

Сначала попробуем задействовать текстовый файл с расширением .psv, для простоты используя только примеры вывода текста, а затем перейдем к полноценным веб-примерам с применением базы данных SQLite.

В начальной строке заголовка файла .psv содержатся имена полей:

name;

• country (символ * означает множество стран);

• area (не обязательно, штат США или другое территориальное образование страны);

• description;

aka (обозначает «также известен как»).

В остальных строках файла описывается по одному существу, поля располагаются в таком порядке и разделяются символом |.

Модуль csv

Пример 17.1 считывает данные о существе в структуры данных Python. Во-первых, файл cryptids.psv, где используется разделение символами вертикальной черты, можно считать с помощью стандартного пакета csv Python, получив список кортежей, где каждый кортеж представляет собой строку данных из файла. (Пакет csv включает также класс DictReader, возвращающий список словарей.) Первая строка этого файла представляет собой заголовок с именами столбцов. Без этого мы могли бы предоставлять заголовки через аргументы для функций csv.

Я включаю в примеры подсказки типов, но вы можете отказаться от них, если у вас более старая версия Python, — код все равно будет работать. Напе­чатаем только заголовок и первые пять строк, чтобы сохранить несколько деревьев.

Пример 17.1. Считывание файла PSV с помощью csv (load_csv.py)

import csv

import sys

def read_csv(fname: str) -> list[tuple]:

    with open(fname) as file:

        data = [row for row in csv.reader(file, delimiter="|")]

    return data

if __name__ == "__main__":

    data = read_csv(sys.argv[1])

    for row in data[0:5]:

        print(row)

Теперь запустите тест из примера 17.2.

Пример 17.2. Тестирование загрузки базы данных CSV

$ python load_csv.py cryptid.psv

['name', 'country', 'area', 'description', 'aka']

['Abaia', 'FJ', ' ', 'Lake eel', ' ']

['Afanc', 'UK', 'CYM', 'Welsh lake monster', ' ']

['Agropelter', 'US', 'ME', 'Forest twig flinger', ' ']

['Akkorokamui', 'JP', ' ', 'Giant Ainu octopus', ' ']

['Albatwitch', 'US', 'PA', 'Apple stealing mini Bigfoot', ' ']

Модуль python-tabulate

Опробуем еще один инструмент с открытым исходным кодом, python-tabulate (https://oreil.ly/L0f6k). Он специально разработан для табличного вывода. Сначала потребуется запустить команду pip install tabulate. В примере 17.3 показан код.

Пример 17.3. Считывание файла PSV с помощью python-tabulate (load_tabulate.py)

from tabulate import tabulate

import sys

def read_csv(fname: str) -> list[tuple]:

    with open(fname) as file:

        data = [row for row in csv.reader(file, delimiter="|")]

    return data

if __name__ == "__main__":

    data = read_csv(sys.argv[1])

    print(tabulate(data[0:5]))

Выполните пример 17.3 в примере 17.4.

Пример 17.4. Запуск скрипта загрузки tabulate

$ python load_tabulate.py cryptid.psv

-----------  -------  ----  -------------------  ---

Name         Country  Area  Description          AKA

Abaia        FJ             Lake eel

Afanc        UK       CYM   Welsh lake monster

Agropelter   US       ME    Forest twig flinger

Akkorokamui  JP             Giant Ainu octopus

-----------  -------  ----  -------------------  ---

Модуль pandas

Два предыдущих примера представляли собой в основном форматоры вывода. Библиотека pandas (https://pandas.pydata.org) — это отличный инструмент для нарезки данных. Он выходит за рамки стандартных структур данных Python, используя такие продвинутые конструкции, как DataFrame (https://oreil.ly/j-8eh) — комбинацию таблицы, словаря и серии. Он может читать .csv и другие файлы с разделителями в виде символов. Пример 17.5 похож на предыдущие примеры, но вместо списка кортежей pandas возвращает DataFrame.

Пример 17.5. Считывание файла PSV с помощью pandas (load_pandas.py)

import pandas

import sys

def read_pandas(fname: str) -> pandas.DataFrame:

    data = pandas.read_csv(fname, sep="|")

    return data

if __name__ == "__main__":

    data = read_pandas(sys.argv[1])

    print(data.head(5))

Выполните пример 17.5 в примере 17.6.

Пример 17.6. Запуск скрипта загрузки pandas

$ python load_pandas.py cryptid.psv

          name country area                  description aka

0        Abaia      FJ                          Lake eel

1        Afanc      UK  CYM           Welsh lake monster

2   Agropelter      US   ME          Forest twig flinger

3  Akkorokamui      JP                Giant Ainu octopus

4   Albatwitch      US   PA  Apple stealing mini Bigfoot

В библиотеке pandas есть множество интересных функций, так что стоит изучить ее более внимательно.

Источник данных SQLite и веб-вывод

В остальных примерах этой главы вы будете считывать данные о существах из базы данных SQLite, используя определенные фрагменты кода веб-сайта из предыдущих глав. Затем нарежете данные на кусочки, кубики и замаринуете их по разным рецептам. Вместо простого вывода текста вы будете устанавливать каждый пример на наш постоянно растущий сайт о криптидах. Вам понадобится несколько дополнений к существующим сервисному, веб-уровню и уровню данных.

Во-первых, потребуется функция веб-уровня и соответствующий HTTP GET-маршрут, чтобы вернуть все данные о существе. И у вас уже есть такой! Сделаем веб-вызов, чтобы получить все данные, но снова покажем лишь первые несколько строк (деревья, знаете ли) в примере 17.7.

Пример 17.7. Запустите тест загрузки существ (в усеченном виде — деревья наблюдают)

$ http -b localhost:8000/creature

[

    {

        "aka": "AKA",

        "area": "Area",

        "country": "Country",

        "description": "Description",

        "name": "Name"

    },

    {

        "aka": " ",

        "area": " ",

        "country": "FJ",

        "description": "Lake eel",

        "name": "Abaia"

    },

...

]

Пакеты диаграмм и графиков

Теперь мы можем перейти от текста к графическим интерфейсам (Graphical User Interface, GUI). Среди наиболее полезных и популярных пакетов Python для графического отображения данных можно назвать следующие:

Matplotlib (https://matplotlib.org) — обширный, но требует некоторого вмешательства для получения красивых результатов;

• Plotly (https://plotly.com/python) — аналогичен Matplotlib и Seaborn, но с акцентом на интерактивных графиках;

• Dash (https://dash.plotly.com) — построен на основе пакета Plotly как своего рода информационная панель;

• Seaborn (https://seaborn.pydata.org) — построен на основе пакета Matplotlib и предлагает интерфейс более высокого уровня, но с меньшим количеством типов графов;

Bokeh (http://bokeh.org) — интегрируется с JavaScript для создания информационных панелей для просмотра очень больших наборов данных.

Как же сделать правильный выбор? Стоит рассмотреть следующие критерии:

• типы графиков (например, диаграмма рассеяния, столбчатая диаграмма, линейный график);

• стилизация;

• простота использования;

• производительность;

• ограничения в данных.

Такие сравнительные исследования, как Top 6 Python Libraries for Visualization: Which One to Use? (https://oreil.ly/10Nsw) пользователя khuyentran1476, могут помочь вам с выбором. В конце концов выбор часто сводится к тому варианту, о котором вы узнаете больше всего. Для этой главы я выбрал пакет Plotly, позволяющий создавать привлекательные графики без написания лишнего кода.

Пример диаграммы 1. Тестирование

Plotly — это библиотека Python с открытым исходным кодом (бесплатная) и несколькими уровнями контроля и детализации:

Plotly Express (https://plotly.com/python/plotly-express) — минимальная библиотека Plotly;

• Plotly (https://plotly.com/python) — основная библиотека;

Dash (https://dash.plotly.com) — инструменты для работы с данными.

Существует также платформа Dash Enterprise (https://dash.plotly.com/dash-enterprise). Она, как и почти все, что имеет в названии слово enterprise — «корпоративный» (включая модели космических кораблей), стоит денег, обычно больших.

Что мы можем показать на основе данных о существах? У диаграмм и графиков есть несколько общих форм:

• столбцовая;

• рассеяния;

• линейная;

• коробчатая (статистическая);

• гистограмма.

Все наши поля данных — строки намеренно минимального размера, чтобы примеры не перегружали логику и этапы интеграции. Для каждого примера будем считывать все данные о существах из базы данных SQLite, используя код из предыдущих глав, а также добавлять функции веб- и сервисного уровня для выбора определенных данных для передачи в функции библиотеки графиков. Сначала установите пакет Plotly и библиотеку, необходимую ему для экспорта изображений:

pip install plotly;

pip install kaleido.

Затем (пример 17.8) добавьте тестовую функцию в файл web/creature.py, чтобы проверить, есть ли у нас нужные фрагменты кода в нужных местах.

Пример 17.8. Добавление тестовой конечной точки (редактирование файла web/creature.py)

# (добавьте эти строки в файл web/creature.py)

from fastapi import Response

import plotly.express as px

@router.get("/test")

def test():

    df = px.data.iris()

    fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")

    fig_bytes = fig.to_image(format="png")

    return Response(content=fig_bytes, media_type="image/png")

В документации обычно рекомендуется вызывать функцию fig.show(), чтобы показать только что созданное изображение, но мы пытаемся соответствовать тому, как это делают FastAPI и Starlette.

Итак, сначала вы получаете fig_bytes (актуальное содержимое bytes изображения). Затем возвращаете пользовательский объект Response.

После того как вы добавили конечную точку в файл web/creature.py и перезапустили веб-сервер (автоматически, если запустили Uvicorn с аргументом --reload), попробуйте получить доступ к новой конечной точке, набрав текст localhost:8000/creature/test в адресной строке браузера. На экране должно появиться изображение, приведенное на рис. 17.1.

Рис. 17.1. Тестовое изображение Plotly

Если вы получили от Uvicorn странную ошибку, например ValueError: 'not' is not a valid parameter name, обновите Pydantic, чтобы исправить это: pip install -U pydantic.

Пример диаграммы 2. Гистограмма

Если все в порядке, начнем работать с данными о существах. Добавим функцию plot() в файл web/creature.py. Мы получим все данные о существах из базы данных с помощью функции get_all() в файлах service/creature.py и data/creature.py. Затем извлечем то, что нам нужно, и с помощью возможностей Plotly выведем различные изображения результатов.

Для первого приема (пример 17.9) просто используем поле name и построим гистограмму, показывающую количество имен существ, начинающихся на каждую букву.

Пример 17.9. Столбчатая диаграмма инициалов имен существ

# (добавьте эти строки в файл web/creature.py)

from collections import Counter

from fastapi import Response

import plotly.express as px

from service.creature import get_all

@router.get("/plot")

def plot():

    creatures = get_all()

    letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

    counts = Counter(creature.name[0] for creature in creatures)

    y = { letter: counts.get(letter, 0) for letter in letters }

    fig = px.histogram(x=list(letters), y=y, title="Creature Names",

        labels={"x": "Initial", "y": "Initial"})

    fig_bytes = fig.to_image(format="png")

    return Response(content=fig_bytes, media_type="image/png")

Введите localhost:8000/creature/plot в адресную строку своего браузера. Вы должны увидеть изображение, приведенное на рис. 17.2.

Рис. 17.2. Гистограмма инициалов имен существ

Пакеты для работы с картами

Если вы введете в поисковую строку Google слова Python и maps, то получите множество ссылок о словарях Python, которые являются встроенным в язык типом отображения, но это не то, о чем сейчас пойдет речь. Поэтому вам, возможно, придется попробовать такие синонимы, как GIS, geo, cartography, spatial и т.д. Некоторые популярные пакеты, приведенные далее, созданы на основе других пакетов из списка:

PyGIS (https://oreil.ly/3QvCz) — ссылки по обработке пространственных данных в Python;

• PySAL (https://pysal.org) — библиотека пространственного анализа Python;

• Cartopy (https://oreil.ly/YnUow) — анализирует и наносит на карту геопространственные данные;

• Folium (https://oreil.ly/72luj) — интегрирован с JavaScript;

• Python Client for Google Maps Services (https://oreil.ly/LWfS5) — API-доступ к Google Maps;

• Geemap (https://geemap.org) — с поддержкой Google Earth;

• Geoplot (https://oreil.ly/Slfvc) — расширяет пакеты Cartopy и Matplotlib;

• GeoPandas (https://geopandas.org) — расширение для нашей любимой библио­теки pandas;

ArcGIS and ArcPy (https://oreil.ly/l7M5C) — интерфейс Esri с открытым исходным кодом.

Как и в случае с пакетами диаграмм/графиков, выбор может зависеть от таких факторов, как:

• типы карт (например, фоновая, векторная, растровая);

• стилизация;

• простота использования;

• производительность;

• ограничения в данных.

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

Пример карты

Для примеров из области картографии я снова использую пакет Plotly — он не слишком прост и не слишком сложен и помогает показать, как интегрировать небольшую веб-карту с FastAPI.

Пример 17.10 иллюстрирует получение двухбуквенных кодов стран ISO для наших существ. Но оказалось, что функция, создающая карты Plotly (фоновая или хороплет, что само по себе звучит как криптид, меняющий форму), хочет использовать вместо этого другой, трехбуквенный стандарт кодов стран ISO. Бр-р-р. Мы могли бы переделать все коды в базе данных и PSV-файле, но проще выполнить команду pip install country_converter и сопоставить один набор кодов стран с другим.

Пример 17.10. Карта стран с криптидами (редактирование файла web/creature.py)

# (добавьте эти строки в файл web/creature.py)

import plotly.express as px

import country_converter as coco

@router.get("/map")

def map():

    creatures = service.get_all()

    iso2_codes = set(creature.country for creature in creatures)

    iso3_codes = coco.convert(names=iso2_codes, to="ISO3")

    fig = px.choropleth(

        locationmode="ISO-3",

        locations=iso3_codes)

    fig_bytes = fig.to_image(format="png")

    return Response(content=fig_bytes, media_type="image/png")

Введите запрос браузеру на получение ответа по адресу localhost:8000/creature/map, и, если повезет, вы увидите карту, на которой выделяются страны с криптидами (рис. 17.3).

Можете увеличить масштаб этой карты, чтобы сосредоточиться на США, используя поле area, представляющее собой двухсимвольный код государства, где country — это US. Задействуйте выражение locationmode="USA-states" и присвойте значение area параметру locations функции px.choropleth().

Рис. 17.3. Карта, на которой выделены страны с криптидами

Заключение

Рядом с вашим домом не рыскали криптиды? Вы можете узнать это из данной главы, где различные инструменты для построения графиков, диаграмм и картографических схем набросали базу данных существ, вызывающих у нас тревогу.


Если есть деревья, похожие на энтов из книг Толкина, не хотелось бы, чтобы они ночью подошли к дверям нашего дома для небольшой беседы.

В английском языке слово map означает и «карта», и «сопоставлять». Перевод различается только по контексту. — Примеч. пер.

Назад: Глава 16. Формы и шаблоны
Дальше: Глава 18. Игры