Внесение последних штрихов в первую версию моей графической библиотеки для Юлии.

Графические данные могут быть очень захватывающими, особенно если вы какое-то время работали над функцией построения графиков, и теперь они наконец-то работают так, как вы этого хотите. Хотя многие параметры по-прежнему отсутствуют и предстоит еще много работы, есть много действительно многообещающих функций, которые уже были устранены. С этими замечательными вещами также приходит толчок к освоению первой версии. Теперь вы можете добавить Hone в свою среду Julia из URL-адреса Github,



"Ноутбук"

"Первая часть"

"Часть вторая"

Если вы следили за этой небольшой серией, вы можете вспомнить, что в предыдущей статье мы оставили две большие дыры в нашем программном обеспечении для построения графиков:

Цвета и ось.

Цвет

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

function Circle(x,y,diameter,fillin)
   # composition = compose(context(), circle(x,y,diameter), fill(string(fillin))
   #     "compose(context(),circle(.5,.5,.1))
    tag = string("circle(",string(x),", ",string(y),", ",string(diameter),"fill(string(","))")
    tree() = introspect(composition)
    show() = composition
    x = x
    y = y
    update(x,y) =  string("circle(",string(x),",",string(y),',',diameter,",fill(string(",Symbol(fillin),")))")
    (var)->(show;composition;tree;tag;x;y;update;diameter)
end

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

color = string("\"",string(fillin),"\"")

Легко, как пирог, теперь все, что нам нужно сделать, это восстановить нашу функцию обновления, и она либо будет работать, либо не работать.

update(x,y) =  string("circle(",string(x),",",string(y),',',diameter,"), fill(", color , "),")

А теперь давайте нарисуем что-нибудь, чтобы проверить это!

x = [5,6,5,5.25,5.5,5.8,5,20]
y = [7,7,4,3.8,3.7,3.8,4,20]
shape = Circle(.5,.5,.01,:blue)
b = Scatter(x,y,shape)

Я мог бы нарисовать это вверх ногами ...

Теперь попробуем изменить цвет на розовый:

x = [5,6,5,5.25,5.5,5.8,5,20]
y = [7,7,4,3.8,3.7,3.8,4,20]
shape = Circle(.5,.5,.01,:pink)
b = Scatter(x,y,shape)

Ось

Еще одна особенность, на которой я остановился, - это возможность рисовать оси x и y. Конечно, ось - это просто простая линия, проведенная от вектора 2 (0,0) к вектору 2 (1,0) для оси X и от (0,0) до (0,1) для оси Y.

Это код, который мы оставили в нашем объекте Line, который позволяет нам делать это:

function Line(pairs, color)
    tree() = introspect(composition)
    show() = composition
    composition = compose(context(), line(pairs), stroke(string(color)))
    pairstring = ""
    for i in pairs
        s = string(i) * ","
        pairstring = pairstring * s
    end
    update(pairs) = string("line([",string(join(pairstring)),"]), stroke(",string(color) ,")", )
    (var)->(show;composition;tree;update;color;pairs)
end

Эта функция следует тому же естественному потоку, что и наша функция формы, где сначала создается композиция, затем следуют любые необходимые входные данные для метаинформации, а затем создается вызываемая функция, которую можно использовать для возврата тега. Часть этой функции, которая, безусловно, выделяется больше всего, - это, вероятно, итерационный цикл, в котором создается пустая строка, которая затем объединяется с отдельными векторами для координат местоположения. Причина, по которой это делается таким образом, заключается в том, что утверждение типа NTuple-to-string Джулии не работает так, как вы могли бы ожидать, и, таким образом, отличный обходной путь - вместо этого преобразовать отдельные dims в строки внутри цикла, а не всю переменную.

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

color = string("\"",string(color),"\"")

Довольно просто…

И теперь мы можем добавить эту строку в нашу функцию обновления:

string("line([",string(join(pairstring)),"]), stroke(", color, "),")

Теперь, когда функция обновления нашего объекта Line действительно работает (я надеюсь), мы можем добавить логику к нашему классу Scatter, чтобы фактически визуализировать линию. Первым шагом будет получение тегов:

axisx = Line([(-1,-1), (-1,1), (1,1)],:blue)
   axisx_tag = axisx.update([(-1,-1), (-1,1), (1,1)])
    axisy = Line([(0,0), (0,1), (0,1)],:blue)
    axisy_tag = axisy.update([(0,0), (0,1), (0,1)])

В отличие от объекта Shape, мы не можем последовательно вызывать один и тот же объект линии с функцией обновления. Это связано с тем, что функцию обновления необходимо сохранить для возврата, который не запутает весь класс. Говоря менее широко, если бы мы добавили сюда возврат:

Затем, когда мы запустили функцию «Line» для создания строки, мы получим результат от функции обновления. Имея это в виду, определенно разумно попытаться избежать одиночного возврата для каждой функции, чтобы мы могли получить тег, не препятствуя функции. Проблема в том, что пары обновляются только каждый раз, когда вызывается итерационный цикл над ним, который, как вы, возможно, помните, необходим для преобразования данных в строку.

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

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

Так что давайте проверим!

Наблюдения

Во-первых, вы могли заметить, что обводка была применена и к кругам. Вероятно, это будет легкое исправление, когда мне просто нужно будет добавить новый контекст к нашему выражению. Еще одна вещь, заслуживающая внимания, - это точка в правом нижнем углу. Эта точка существует, потому что в противном случае перевернутый смайлик был бы прямо на краю сюжета. Я думаю, это определенно иллюстрирует необходимость полей как параметра внутри объекта scatter.

Построение реальных данных!

Теперь, когда наш график рассеяния работает, пришло время проверить его на реальных данных! В этом примере я собираюсь использовать свой небольшой файл «WeatherHistory.csv», который я не знаю, где я получил или сколько времени у меня было…

Симптом помешательства на данных.

Как обычно, первое, что нам нужно сделать, это прочитать данные с помощью CSV.jl.

Теперь (к сожалению) нам нужно будет показать наши данные с параметром allcols, установленным в значение true.

И начинается отсев данных ...

Найдя столбцы, которые я хотел построить, я перенес их в переменные с именами «x» и «y», чтобы мы могли их использовать.

x = df[Symbol("Wind Speed (km/h)")]
y = df[Symbol("Temperature (C)")]

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

shape = Circle(.5,.5,.10,:lightblue)

А затем мы можем вставить это в нашу функцию разброса:

plot = Scatter(x,y,shape)

Итак ... Это убило ядро, но не могу сказать, что я удивлен.

На самом деле, он убил не только мое ядро, я думаю, он убил и Вэйланда. Мой терминал очистился, то есть он вернулся домой и полностью отключил мой сервер Jupyter.

Очень странно.

После ограничения количества димов для моих массивов X и Y до 700 с помощью цикла for (держу пари, что есть функция по умолчанию, которую я мог бы использовать), мы наконец получили результат! Удивительно, но на самом деле это заняло не так много времени, как я ожидал. Я взял на себя смелость синхронизировать функцию, чтобы мы могли хорошо рассмотреть как скорость вычислений, так и распределения.

Следующее, что я решил сделать, это сравнить это с Plots.jl, стандартной библиотекой, используемой для построения графиков в Julia. Проблема с модулем Plots в том, что он зависит от GR, Plot.ly и Matplotlib. Хотя это довольно интересно, поскольку все они представляют собой невероятно разные библиотеки и иметь их внутри одного модуля, безусловно, круто и странно, я не думаю, что использование кода Pythonic для построения графиков в Julia является очень хорошей идеей для повышения производительности. Первое, что я сделал для сравнения, это установил ограничитель на 3000 димов для относительно базового теста. Вот результаты:

А для Plots.jl:

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

Вывод

Есть еще много вещей, которые мне нужно завершить с Hone, так как он все еще находится на ранней стадии своего существования. Я предполагаю, что вся работа, которую я делаю сейчас, позволит невероятно легко добавлять новые функции и формы, потому что все, что для этого потребуется, - это добавление мета-выражения. Еще одним большим преимуществом использования мета является то, что каждый отдельный объект будет по-прежнему храниться в памяти и полностью настраиваться конечным пользователем, поскольку он хранится в том же выражении, которое, как вы могли вспомнить, распечатывается, когда для параметра debug установлено значение true. Самая большая проблема, которую я беспокою по поводу Hone, - это, вероятно, производительность. Хотя, безусловно, есть некоторые оптимизации, которые я мог бы сделать прямо сейчас, я опасаюсь, что большая часть оптимизации может привести к полной переработке всего модуля, подобно тому, что случилось с Lathe. Хотя это может быть неизбежно, поскольку мне нравится постоянно улучшать программное обеспечение, над которым я работаю, я бы очень предпочел не переписывать модуль.

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

но это не должно быть невозможным.

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

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