Хабрахабр

Julia. Генераторы отчётов и документации

Поскольку Julia — язык, пользователи которого непосредственно связаны с задачами анализа данных, подготовки статей и красивых презентаций с результатами расчётов и отчётов, то эту тему просто нельзя обойти мимо. Одной из актуальных проблем во все времена, является проблема подготовки отчётов.

Поэтому сюда включены средства по критерию возможности внедрения выполняемого кода на Julia в шаблон с некоторой разметкой. Изначально эта статья планировалась набор рецептов для генерации отчётов, однако рядом с отчётами находится тема документирования, с которой у генераторов отчётов много пересечений. Ну и, естественно, не остались без внимания некоторые ключевые моменты самого языка Julia, без которых может быть не ясно, в каких случаях и какие средства стоит использовать. Наконец, отметим, что в обзор вошли генераторы отчётов как реализованные на самой Julia, так и средства, написанные на других языках программирования.

Jupyter notebook

Благодаря возможности подключения различных вычислительных ядер, он популярен у исследователей и математиков, привыкших к своим специфическим языкам программирования, одним из которых является Julia. Этот инструмент, пожалуй, следует отнести к наиболее популярным у тех, кто занимается анализом данных. И именно поэтому Notebook здесь упоминается.
Процесс установки Jupyter Notebook не сложен. Соответствующие модули для языка Julia реализованы для Jupyter Notebook полностью. https://github.com/JuliaLang/IJulia.jl Если же Jupyter Notebook уже установлен, то необходимо лишь установить пакет Ijulia и зарегистрировать соответствующее вычислительное ядро. Порядок см.

Блокнот (будем использовать блокнотную терминологию) в Jupyter Notebook состоит из блоков, каждый из которых может содержать либо код, либо разметку в различных её видах (например Markdown). Поскольку продукт Jupyter Notebook достаточно известен, чтобы не расписывать его подробно, упомянем лишь пару моментов. Если в конце строки с кодом поставлена точка с запятой, результат не будет выведен на экран. Результат обработки — это либо визуализация разметки (текст, формулы и пр.), либо результат выполнения последней операции.

Блокнот до выполнения приводится на следующем рисунке: Примеры.

Результат его выполнения приводится на следующем рисунке

Отметим, что для вывода матрицы возможно использование типа DataFrame, для которого результат отображается в виде html-таблицы с явными границами и скроллером, если он нужен. Блокнот содержит графику и некоторый текст.

При наличии установленных средств преобразования, может преобразовать и в pdf. Jupyter notebook может экспортировать текущий блокнот в файл в формате html.

Для построения отчётов по некоторому регламенту, можно использовать модуль nbconvert и следующую команду, вызываемую в фоновом режиме по расписанию:
jupyter nbconvert --to html --execute julia_filename.ipynb

При выполнении длительных вычислений целесообразно добавить опцию с указанием таймаута — --ExecutePreprocessor.timeout=180

Опция --execute здесь означает принудительный запуск пересчёта. В текущей директории появится html-отчёт, сформированный из этого файла.

Полный набор опция модуля nbconvert см.
https://nbconvert.readthedocs.io/en/latest/usage.html

Результат преобразования в html практически полностью соответствует предыдущему рисунку, за исключением того, что в нём отсутствует панели меню и кнопок.

Jupytext

Достаточно интересная утилита, которая позволяет конвертировать уже созданные ранее ipynb-заметки в текст Markdown или код на Julia.

Ранее рассмотренный пример мы можем преобразовать с помощью команды
jupytext --to julia julia_filename.ipynb

В результате чего получим файл julia_filename.jl с кодом на Julia и специальную разметку в форме комментариев.

# ---
# jupyter:
# jupytext:
# text_representation:
# extension: .jl
# format_name: light
# format_version: '1.3'
# jupytext_version: 0.8.6
# kernelspec:
# display_name: Julia 1.0.3
# language: julia
# name: julia-1.0
# --- # # Report example using Plots, DataFrames # ### Drawing
# Good time to show some plot plot(rand(5,5), linewidth=2, title="My Plot", size = (500, 200)) # ## Some computational results rand(2, 3) DataFrame(rand(2, 3))

Разделители блоков заметки — просто двойной перевод строки.

Обратное преобразование мы можем сделать при помощи команды:
jupytext --to notebook julia_filename.jl

В итоге, будет сгенерирован ipynb-файл, который, в свою очередь, можно обработать и преобразоваить в pdf или html.

подробнее https://github.com/mwouts/jupytext См.

Общий недостаток jupytext и jupyter notebook — «красивость» отчёта ограничена возможностями этих инструментов.

Самостоятельная генерация HTML

Однако здесь придётся немного погрузиться в особенности формирования изображений. Если по какой-то причине мы считаем, что Jupyter Notebook является слишком тяжелым продуктом, требующим установки многочисленных сторонних пакетов, не нужных для работы Julia, или недостаточно гибким для построения нужной нам формы отчета, то альтернативным способом является генерация html-страницы вручную.

Причем, для различных запрошенных mime-способов вывода могут быть различные варианты отображения. Для Julia типичным способом вывода чего-либо в поток вывода является использование функции Base.write, а для декорирования — Base.show(io, mime, x). Например, DataFrame при выводе как текст отображается таблицей с псевдографикой.

julia> show(stdout, MIME"text/plain"(), DataFrame(rand(3, 2)))
3×2 DataFrame
│ Row │ x1 │ x2 │
│ │ Float64 │ Float64 │
├─────┼──────────┼───────────┤
│ 1 │ 0.321698 │ 0.939474 │
│ 2 │ 0.933878 │ 0.0745969 │
│ 3 │ 0.497315 │ 0.0167594 │

Если же, mime указан как text/html, то итогом является HTML-разметка.

julia> show(stdout, MIME"text/html"(), DataFrame(rand(3, 2)))
<table class="data-frame"> <thead> <tr><th></th><th>x1</th><th>x2</th></tr> <tr><th></th><th>Float64</th><th>Float64</th></tr> </thead> <tbody><p>3 rows × 2 columns</p> <tr><th>1</th><td>0.640151</td><td>0.219299</td></tr> <tr><th>2</th><td>0.463402</td><td>0.764952</td></tr> <tr><th>3</th><td>0.806543</td><td>0.300902</td></tr> </tbody>
</table>

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

Если нам необходимо сформировать один единственный файл html, то изображение должно быть внедрено в код страницы. Сложнее обстоит ситуация с изображениями.

Вывод в файл будем выполнять функцией Base.write, для которой определим соответствующие методы. Рассмотрим пример, в котором это реализовано. Итак, код:

#!/usr/bin/env julia using Plots
using Base64
using DataFrames # сформируем изображение и запомним его в переменной
p = plot(rand(5,5), linewidth=2, title="My Plot", size = (500, 200)) # распечатаем тип, чтобы видеть, кто формирует изображение
@show typeof(p) # => typeof(p) = Plots.Plot # Определим три абстрактных типа, чтобы сделать 3 разных # метода функции преобразования изображений
abstract type Png end
abstract type Svg end
abstract type Svg2 end # Функция Base.write используется для записи в поток
# Определим свои методы этой функции с разными типами # Первый вариант — выводим растровое изображение, перекодировав
# его в Base64-формат.
# Используем HTML разметку img src="data:image/png;base64,..."
function Base.write(file::IO, ::Type{Png}, p::Plots.Plot) local io = IOBuffer() local iob64_encode = Base64EncodePipe(io); show(iob64_encode, MIME"image/png"(), p) close(iob64_encode); write(file, string("<img src=\"data:image/png;base64, ", String(take!(io)), "\" alt=\"fig.png\"/>\n"))
end # Два метода для вывода Svg
function Base.write(file::IO, ::Type{Svg}, p::Plots.Plot) local io = IOBuffer() show(io, MIME"image/svg+xml"(), p) write(file, replace(String(take!(io)), r"<\?xml.*\?>" => "" ))
end # выводим в поток XML-документ без изменений, содержащий SVG
Base.write(file::IO, ::Type{Svg2}, p::Plots.Plot) = show(file, MIME"image/svg+xml"(), p) # Определим метод функции для DataFrame
Base.write(file::IO, df::DataFrame) = show(file, MIME"text/html"(), df) # Пишем файл out.html простейший каркас HTML
open("out.html", "w") do file write(file, """
<!DOCTYPE html>
<html> <head><title>Test report</title></head> <body> <h1>Test html</h1> """) write(file, Png, p) write(file, "<br/>") write(file, Svg, p) write(file, "<br/>") write(file, Svg2, p) write(file, DataFrame(rand(2, 3))) write(file, """ </body>
</html> """)
end

GRBackend, который может выполнить растровый или векторный вывод изображения. Для формирования изображений по умолчанию используется движок Plots. MIME"image/png"() формирует изображение в формате png. В зависимости от того, какой тип указан в аргументе mime функции show, формируется соответствующий результат. Однако во втором случае, следует обратить внимание на то, что формируется полностью самостоятельный xml-документ, который может быть записан как отдельный файл. MIME"image/svg+xml"() приводит к генерации svg-изображения. Именно поэтому в методе Base.write(file::IO, ::Type{Svg}, p::Plots. В то же время, наша цель — вставить изображение в HTML страницу, что в HTML5 можно сделать путём простой вставки SVG-разметки. Хотя, большинство браузеров, способно корректно показывать изображение даже в этом случае. Plot) вырезается xml-заголовок, который, иначе, будет нарушать структуру HTML-документа.

Plot), особенностью внедрения здесь является то, что бинарные данные мы можем вставить внутрь HTML только в Base64-формате. Касаемо метода для растровых изображений Base.write(file::IO, ::Type{Png}, p::Plots. А для перекодирования используем Base64EncodePipe. Делаем это при помощи конструкции<img src="data:image/png;base64,"/>.

Метод Base.write(file::IO, df::DataFrame) обеспечивает вывод в формате html-таблицы объекта DataFrame.

Итоговая страница выглядит следующим образом:

Одно является растровым, значит не может быть увеличено без потери детализации. На изображении все три картинки выглядят примерно одинаково, однако помним, что одно из них вставлено некорректно с точки зрения HTML (лишний xml-заголовок). И оно же может быть легко масштабировано без потери деталей. И только одно из них вставлено как правильный svg-фрагмент внутри разметки HTML.

Но любые визуальные улучшения возможны при помощи CSS. Естественно, страница получилась очень простой.

Например, необходимо выполнить группировку данных по какому-то полю. Такой способ формирования отчётов полезен, например, тогда, когда количество выводимых таблиц определяется реальными данными, а не шаблоном. Поскольку при формировании страницы результат определяется количеством вызовов Base.write, то очевидно, что нет никаких проблем обернуть нужный блок в цикл, сделать вывод зависимым от данных и пр. И по каждой группе сформировать отдельные блоки.

Пример кода:

using DataFrames # Химические элементы и их аггрегатное состояние
ptable = DataFrame( Symbol = ["H", "He", "C", "O", "Fe" ], Room = [:Gas, :Gas, :Solid, :Gas, :Solid]
) res = groupby(ptable, [:Room]) # А теперь выведем группы раздельно
open("out2.html", "w") do f for df in (groupby(ptable, [:Room])) write(f, "<h2>$(df[1, :Room])</h2>\n") show(f, MIME"text/html"(), DataFrame(df)) write(f, "\n") end
end

Результат работ этого скрипта — фрагмент HTML странички.

В то же время, всё, что требует преобразования, выводится через Base.show. Обратите внимание на то, что всё, что не требует декорирования/преобразования формата, выводится напрямую через функцию Base.write.

Weave.jl

Использует идеи генераторов Pweave, Knitr, rmarkdown, Sweave. Weave — это генератор научных отчётов, который реализован на Julia. И, даже в IJulia Notebooks и обратно. Основной его задачей декларируется экспорт исходной разметки на любом из предлагаемых языков (Noweb, Markdown, скриптовый формат) в разметку LaTex, Pandoc, Github markdown, MultiMarkdown, Asciidoc, reStructuredText форматы. В части последнего он похож на Jupytext.

И это очень полезный инструмент именно для научных сотрудников. То есть, Weave — средство, позволяющее писать шаблоны, содержащие Julia-код, на различных языках разметки, а на выходе иметь разметку на другом языке (но уже с результатами выполнения Julia-кода). Weave сгенерирует файл для финальной статьи. Например, можно подготовить статью на Latex, которая будет иметь вставки на Julia с автоматическим вычислением результата и его подстановкой.

Это позволяет выполнять разрабатывать и отлаживать внедренные в разметку скрипты на Julia, после чего сгенерировать целевой файл. Существует поддержка редактора Atom при помощи соответствующего плагина https://atom.io/packages/language-weave.

Результат выполнения кода может быть выведен в итоговый отчёт. Основной принцип в Weave, как уже было упомянуто, это разбор шаблона, содержащего разметку с текстом (формулами и пр) и вставки кода на Julia. Вывод текста, кода, вывод результатов, вывод графиков — всё это может быть индивидуально настроено.

То есть, шаблоны отдельно, обработчики отдельно. Для обработки шаблонов необходимо запустить внешний скрипт, который соберёт всё в единый документ и преобразует в нужный выходной формат.

Пример такого скрипта обработки:

# Обработать файлы и сгенерировать отчёты:
# Markdown
weave("w_example.jmd", doctype="pandoc" out_path=:pwd) # HTML
weave("w_example.jmd", out_path=:pwd, doctype = "md2html") # pdf
weave("w_example.jmd", out_path=:pwd, doctype = "md2pdf")

jmd в именах файлов — Julia Markdown.

Однако вставим ему заголовок со сведениями об авторе, которые понимает Weave. Возьмём тот же пример, который мы использовали в предыдущих средствах.

---
title : Intro to Weave.jl with Plots
author : Anonymous
date : 06th Feb 2019
--- # Intro ## Plot
` ``{julia;}
using Plots, DataFrames plot(rand(5,5), linewidth=2, title="My Plot", size = (500, 200))
` `` ## Some computational results
` ``julia
rand(2, 3)
` `` ` ``julia
DataFrame(rand(2, 3))
` ``

Этот фрагмент, будучи преобразованным в pdf, выглядит примерно следующим образом:

Шрифты и оформление — хорошо узнаваемы для пользователей Latex.

Для каждого фрагмента внедрённого кода можно определить то, каким образом этот код будет обработан, и что будет выведено в итоге.

Например:

  • echo = true — код будет выведен в отчёт
  • eval = true — результат выполнения кода будет выведен в отчёт
  • label — добавить метку. Если используется Latex, то это будет использовано как fig:label
  • fig_width, fig_height — размеры изображения
  • и пр.

http://weavejl.mpastell.com/stable/ О форматах noweb и script, а также подробнее об этом инструменте см.

Literate.jl

Задачей этого инструмента является генерация документов на основе кода Julia, содежащего комментарии в формате markdown. Авторы этого пакета на вопрос, почему Literate, отсылают к парадигме Literate Programming Дональда Кнутта. Однако инструмент легковесен и, в первую очередь, ориентирован на документирование кода. В отличии от предыдущего рассмотренного инструмента Weave, документы с результатами выполнения он сделать не может. Часто используется в цепочке других инструментов документирования, например вместе с Documenter.jl. Например, помощь в написании красивых примеров, которые могут быть размещены на любой markdown-платформе.

Ни в одном из них выполнение внедрённого кода проводиться не будет. Возможно три варианта выходного формата — markdown, notebook и script (чистый Julia код).

Пример исходного файла с комментариями Markdown (после первого символа # ):

#!/usr/bin/env julia using Literate
Literate.markdown(@__FILE__, pwd()) # documenter=true # # Intro # ## Plot
using Plots, DataFrames plot(rand(5,5), linewidth=2, title="My Plot", size = (500, 200)) # ## Some computational results
rand(2, 3) DataFrame(rand(2, 3))

Результатом его работы будет Markdown-документ и директивы для Documenter, если их генерация не была явно отключена.

` ``@meta
EditURL = "https://github.com/TRAVIS_REPO_SLUG/blob/master/"
` `` ` ``@example literate_example
#!/usr/bin/env julia using Literate
Literate.markdown(@__FILE__, pwd(), documenter=true)
` `` # Intro ## Plot ` ``@example literate_example
using Plots, DataFrames plot(rand(5,5), linewidth=2, title="My Plot", size = (500, 200))
` `` ## Some computational results ` ``@example literate_example
rand(2, 3) DataFrame(rand(2, 3))
` `` *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*

Вставки кода внутри markdown здесь намеренно поставлены с пробелом между первым и последующими апострофами, чтобы не испортились при публикации статьи.

подробнее https://fredrikekre.github.io/Literate.jl/stable/ См.

Documenter.jl

Его основное назначение — формирование пригодной для чтения документации пакетов, написанных на Julia. Генератор документации. Documenter преобразует в html или pdf как примеры с Markdown-разметкой и внедрённым Julia-кодом, так и исходные файлы модулей, извлекая Julia-docstrings (собственные коментарии Julia).

Пример типового оформления документации:

Однако некоторые моменты Documenter мы здесь рассмотрим. В этой статье мы не будем подробно останавливаться на принципах документирования, поскольку, по-хорошему, это надо делать в рамках отдельной статьи, посвященной разработке модулей.

Правая сторона — собственно, текст документации. Прежде всего, стоит обратить внимание на то, что экран разбит на две части — левая сторона содержит интерактивное оглавление.

Типовая структура директорий с примерами и документацией выглядит следующим образом:

docs/ src/ make.jl src/ Example.jl ...

А примеры находятся где-то в общей директории исходных кодов src. Директория docs/src — это markdown документация.

Содержимое этого файла для самого Documenter: Ключевой файл для Docuementer – docs/make.jl.

using Documenter, DocumenterTools makedocs( modules = [Documenter, DocumenterTools], format = Documenter.HTML( # Use clean URLs, unless built as a "local" build prettyurls = !("local" in ARGS), canonical = "https://juliadocs.github.io/Documenter.jl/stable/", ), clean = false, assets = ["assets/favicon.ico"], sitename = "Documenter.jl", authors = "Michael Hatherly, Morten Piibeleht, and contributors.", analytics = "UA-89508993-1", linkcheck = !("skiplinks" in ARGS), pages = [ "Home" => "index.md", "Manual" => Any[ "Guide" => "man/guide.md", "man/examples.md", "man/syntax.md", "man/doctests.md", "man/latex.md", hide("man/hosting.md", [ "man/hosting/walkthrough.md" ]), "man/other-formats.md", ], "Library" => Any[ "Public" => "lib/public.md", hide("Internals" => "lib/internals.md", Any[ "lib/internals/anchors.md", "lib/internals/builder.md", "lib/internals/cross-references.md", "lib/internals/docchecks.md", "lib/internals/docsystem.md", "lib/internals/doctests.md", "lib/internals/documenter.md", "lib/internals/documentertools.md", "lib/internals/documents.md", "lib/internals/dom.md", "lib/internals/expanders.md", "lib/internals/mdflatten.md", "lib/internals/selectors.md", "lib/internals/textdiff.md", "lib/internals/utilities.md", "lib/internals/writers.md", ]) ], "contributing.md", ],
) deploydocs( repo = "github.com/JuliaDocs/Documenter.jl.git", target = "build",
)

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

Их формат — ```@something Documenter поддерживает ряд директив для вставки кода.

  • @docs, @autodocs — ссылки на docstrings документацию, извлеченную из Julia-файлов.
  • @ref, @meta, @index, @contents — ссылки, указания индекстых страниц и пр.
  • @example, @repl, @eval — режимы выполнения внедрённого кода на Julia.
  • ...

Причём упомянутый ранее Literate.jl, может автоматически сгенерировать такую разметку, что и было продемонстрировано ранее. Наличие директив @example, @repl, @eval, по сути, и определило то, включать ли Documenter в это обзор или нет. То есть, принципиальных ограничений к использованию генератора документации как генератора отчётов здесь нет.

https://juliadocs.github.io/Documenter.jl/stable/ Подробнее о Documenter.jl см.

Заключение

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

Genie.jl — это попытка реализовать Julia on Rails, а Flax — своеобразный аналог eRubis со вставками кода на Julia. В статье не рассмотрен генератор Flax из состава пакета Genie.jl. Однако Flax не предоставляется в виде отдельного пакета, а Genie не включена в основной репозиторий пакетов, поэтому он и не вошел в этот обзор.

Результат их работы может также быть использован как часть отчётов, но об этом также следует писать отдедльную статью. Отдельно хотелось бы упомянуть пакеты Makie.jl и Luxor.jl, обеспечиющие формирование сложных векторных визуализаций.

Ссылки

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть