В этой статье приведен перевод руководства: "How to quickly turn your Julia code into a web app with Genie Builder". В статье пойдет речь о новом инструменте быстрой разработки web-приложений на основе Genie Framework - платформы для интернет приложений на языке Julia.

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

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

  • Другие пользователи могут повторно запустить анализ и точно настроить аналитические данные с помощью элементов управления в пользовательском интерфейсе (UI).

Все это без написания ни единой строки HTML или Javascript благодаря low-code/no-code решению Genie Builder - модуля расширения для среды программирования Visual Studio Code в виде визуального редактора.

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

Содержание:

  • Написание кода для анализа данных

  • Создание нового приложения в Genie Builder

  • Разработка пользовательского интерфейса в редактора Genie Builder

  • Реализация логики приложения

  • Запуск приложения вне Genie Builder

1. Написание кода для анализа данных

Чтобы проиллюстрировать рабочий процесс Genie, мы напишем некоторый код для извлечения информации из немецкого набора данных о кредитных рисках. Этот набор данных содержит информацию о 1000 людях, где каждый человек описывается по 20 признакам, таким как возраст, пол или сумма кредита. Кроме того, каждый пользователь был вручную помечен как "хороший кредитный риск" или "плохой кредитный риск" в соответствии со значениями характеристик.

Допустим, мы хотим извлечь из набора данных следующие данные:

  • Количество и денежная сумма в хороших и плохих кредитах.

  • Суммы кредитов и возраст кредиторов.

  • Количество кредитов в разбивке по возрастному диапазону.

Чтобы рассчитать эти показатели, сохраните приведенный ниже код в скрипте german credits.jl и выполните его в Julia REPL, чтобы убедиться, что все работает. (Примечание переводчика: Не забудьте скачать набор данных (german_credits.csv) о кредитных рисках из репозитория https://github.com/GenieFramework/GenieBuilderDemos, так как на сайте Kaggle выложена не полная версия файла и скрипт работать не будет).

using DataFrames, Dates, OrderedCollections
 
 function good_bad_credits_stats(data::DataFrame)
     good_credits_count = data[data.Good_Rating .== true, [:Good_Rating]] |> nrow
     bad_credits_count = data[data.Good_Rating .== false, [:Good_Rating]] |> nrow
     good_credits_amount = data[data.Good_Rating .== true, [:Amount]] |> Array |> sum
     bad_credits_amount = data[data.Good_Rating .== false, [:Amount]] |> Array |> sum
 
     (; good_credits_count, bad_credits_count, good_credits_amount, bad_credits_amount)
 end
 
 function credit_age_amount(data::DataFrame; good_rating::Bool)
     data[data.Good_Rating .== good_rating, [:Age, :Amount]]
 end
 
 function credit_no_by_age(data::DataFrame; good_rating::Bool)
     age_stats::LittleDict{Int,Int} = LittleDict()
     for x in 20:10:90
       age_stats[x] = data[(data.Age .∈ [x:x+10]) .& (data.Good_Rating .== good_rating), [:Good_Rating]] |> nrow
     end
     age_stats
 end
 
 # testing the functions
 using CSV
 data = CSV.File("german_credits.csv") |> DataFrame
 @show good_bad_credits_stats(data)
 @show credit_age_amount(data, good_rating=true)
 @show credit_no_by_age(data, good_rating=true)

Теперь, когда код анализа завершен и работает, пришло время превратить его в приложение платформы Genie.

2. Создание нового приложения в Genie Builder

Для создания приложения мы будем использовать плагин Genie Builder (далее GB) для VSCode. После установки плагина запустите сервер GB и создайте новое приложение под названием German Credits. Нажмите "Да", когда вас спросят, хотите ли вы его запустить, и появится визуальный редактор.

Примечание переводчика: после установки плагина GenieBuilder рекомендуется перезагрузить VSCode. Также при первом старте сервера - загрузка может занять значительное время, так как в окружение будут установлены необходимые пакеты.

На вкладке рабочая область вы обнаружите, что код вновь созданного приложения разделен на два файла: app.jl и app.jl.html . Из двух, app.jl является тем, который должен быть отредактирован вручную, тогда как app.jl.html формируется визуальным редактором. На этом этапе вы можете добавить скрипт germancredits.jl перейдите в рабочую область, чтобы все ваши файлы были в одном месте.

Чтобы избежать конфликтов, рекомендуется обернуть код в модуль и удалить любой тестовый код. Итак, отредактируйте germancredits.jl, чтобы это выглядело так:

module GermanCredits
     export good_bad_credits_stats, credit_age_amount_duration, credit_data_by_age
     using DataFrames, Dates, OrderedCollections
 
     function good_bad_credits_stats(data::DataFrame)
         ...
     end
     function credit_age_amount(data::DataFrame; good_rating::Bool)
         ...
     end
     function credit_no_by_age(data::DataFrame; good_rating::Bool)
         ...
     end
 end

Чтобы установить необходимые пакеты DataFrames, Dates и OrderedCollections в приложении, запустите менеджер пакетов Genie Builder, щелкнув правой кнопкой мыши на названии приложения на левой боковой панели. Вы увидите ход установки в REPL, и версии пакетов будут добавлены в файл Project.toml.

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

3. Разработка пользовательского интерфейса в редактора Genie Builder

Для пользовательского интерфейса German Credits мы хотим иметь:

  • Заголовок страницы.

  • Значки, показывающие количество кредитов и их совокупную стоимость в денежном эквиваленте.

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

  • Столбчатая диаграмма, отображающая количество кредитов в разбивке по возрасту.

  • Точечная диаграмма, отображающая возраст в зависимости от суммы для каждого кредита.

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

Затем добавьте следующие компоненты:

  • Абзац (x4).

  • Диапазон.

  • Бар.

  • Разброс.

Наконец, давайте увеличим шрифт значков с цифрами. Нажмите на значок </> и измените класс элемента <p> на bignumber. Затем добавьте новый стиль CSS .bignumber{font-size:40px; text-align:center;}.

Чтобы просмотреть пользовательский интерфейс, нажмите на значок монитора рядом с вкладкой "GET" на левой боковой панели. Обратите внимание, что пока ничего не работает, вам все равно придется реализовать логику приложения и привязать компоненты пользовательского интерфейса к коду Julia.

4. Реализация логики приложения

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

  • Предоставление реактивных переменных из кода Julia пользовательскому интерфейсу.

  • Написание реактивного кода для управления взаимодействием с пользователем.

  • Привязка открытых переменных к компонентам пользовательского интерфейса.

Файл app.jl в рабочей области является основной точкой входа в приложение, и именно в этом файле вы будете писать логику. Вот как выглядит файл по умолчанию:

 using GenieFramework
 @genietools
 
 @handlers begin
   @out message = "Hello World!"
   @onchange isready begin
     @show "App is loaded"
   end
 end
 
 @page("/", "app.jl.html")

Чтобы начать с чистого приложения, удалите содержимое блока, разделенного макросом @handlers. Затем включите пакет German Credits и загрузите набор данных, добавив перед ним следующий код:

include("germancredits.jl")
 using .GermanCredits
 using GenieFramework, CSV, DataFrames, OrderedCollections
 
 const data = CSV.File("german_credits.csv") |> DataFrame

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

Селектор выбора диапазона

В приложениях Genie реактивность основана на двух концепциях:

  • Переменные отображаются в пользовательском интерфейсе путем пометки их макросом @in, если они принимают свое значение из пользовательского интерфейса, или макросом @out, если они выводят свое значение в пользовательский интерфейс.

  • Код для обработки взаимодействий записан в блоке, ограниченном макросом @onchange, который отслеживает переменную и всякий раз, когда она изменяется, выполняет код в блоке.

Чтобы отфильтровать данные с помощью селектора диапазона в пользовательском интерфейсе, нам нужно:

  • Переменная типа RangeData для хранения диапазона, выбранного в пользовательском интерфейсе.

  • DataFrame для хранения отфильтрованных данных.

  • Вызов функции для обновления доступных данных при выборе возрастного диапазона.

Чтобы реализовать эти элементы, добавьте следующий код внутри макроса @handlers:

 @handlers begin
     @in age_range::RangeData{Int} = RangeData(18:90)
     @out filtered_data = DataFrame()
     @onchange age_range begin
         filtered_data = data[(age_range.range.start .<= data[!, :Age] .<= age_range.range.stop), :]
     end
 end

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

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

Номерные значки

Чтобы отобразить номер и сумму в кредитах, вам потребуется:

  • Переменные для хранения величин.

  • Вызов good_bad_credits_stats для обновления переменных при выборе нового диапазона.

@handlers begin
     ...
     @out good_credits_count = 0 
     @out bad_credits_count = 0
     @out good_credits_amount = 0
     @out bad_credits_amount = 0
     @onchange age_range begin
         ...
         good_credits_count, bad_credits_count, good_credits_amount,
         bad_credits_amount = good_bad_credits_stats(filtered_data)
     end
 end

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

Количество кредитов в разбивке по возрасту

Мы построим график кредитов по возрасту с помощью гистограммы, проиндексированной по возрастным диапазонам с шагом в 10 лет. Для этого вам понадобится:

  • Массив для хранения меток по оси x.

  • Массив PlotData для хранения данных графика.

  • Переменная типа PlotLayout для указания макета участка.

  • Реактивный код для обновления графиков значениями, рассчитанными с помощью credit_no_by_age.

 @handlers begin
     ...
     @out age_slots = ["20-30", "30-40", "40-50", "50-60", "60-70", "70-80", "80-90"]
     @out credit_no_by_age_plot = PlotData[]
     @out credit_no_by_age_plot_layout = PlotLayout(barmode="group", showlegend = true)
     @onchange age_range begin
         ...
         credit_no_by_age_plot = [
             PlotData(
                 x = age_slots, 
                 y = collect(values(credit_no_by_age(filtered_data; good_rating = true))),
                 name = "Good credits", 
                 plot = StipplePlotly.Charts.PLOT_TYPE_BAR, 
                 marker = PlotDataMarker(color = "#72C8A9")
             ),
             PlotData(
                 x = age_slots, 
                 y = collect(values(credit_no_by_age(filtered_data; good_rating = false))),
                 name = "Bad credits", 
                 plot = StipplePlotly.Charts.PLOT_TYPE_BAR, 
                 marker = PlotDataMarker(color = "#BD5631")
             )
         ]
     end
 end

Точечный график зависимости возраста от суммы кредита

Для этого сюжета вам потребуется:

  • Массив данных графика для хранения данных графика.

  • Переменная типа Plot Layout для указания макета участка.

 @handlers begin
     ...
     @out age_amount_plot_layout = PlotLayout(showlegend = true)
     @out age_amount_plot = PlotData[]
 
     @onchangeany age_range begin
         ...
         dgood = credit_age_amount(filtered_data; good_rating = true)
         dbad = credit_age_amount(filtered_data; good_rating = false)
         age_amount_plot = [
             PlotData(
                 x = dgood.Age, 
                 y = dgood.Amount, 
                 name = "Good credits", 
                 mode = "markers",
                 marker = PlotDataMarker(size=18, opacity= 0.4, color = "#72C8A9", symbol="circle")
             ),
             PlotData(
                 x = dbad.Age, 
                 y = dbad.Amount, 
                 name = "Bad credits", 
                 mode = "markers",
                 marker = PlotDataMarker(size=18, opacity= 0.4, color = "#BD5631", symbol="cross")
             )
         ]
     end
 end

И с этим последним компонентом вы закончили свое приложение Genie!

5. Запуск приложения вне Genie Builder

Вы можете запустить свое приложение локально в VSCode из меню приложений GenieBuilder на боковой панели. Однако, если вы хотите запустить свое приложение за пределами GenieBuilder, файлы приложения находятся в папке ~/.julia/geniebuilder/apps/GermanCredits/. Перед запуском приложения вам нужно будет сообщить Genie о запуске сервера, добавив следующую строку в конце app.jl:

Server.isrunning() || Server.up()

Затем откройте REPL с помощью julia --project в папке приложения и введите include("app.jl"), чтобы запустить приложение app.

P.S. дополнительно вы можете посмотреть обучающее видео по использованию Genie Builder v0.2 на YouTube:

Комментарии (0)