Внесение последних штрихов в первую версию моей графической библиотеки для Юлии.
Графические данные могут быть очень захватывающими, особенно если вы какое-то время работали над функцией построения графиков, и теперь они наконец-то работают так, как вы этого хотите. Хотя многие параметры по-прежнему отсутствуют и предстоит еще много работы, есть много действительно многообещающих функций, которые уже были устранены. С этими замечательными вещами также приходит толчок к освоению первой версии. Теперь вы можете добавить 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 наблюдений, что, безусловно, много для построения графика.
но это не должно быть невозможным.
Я также намерен максимально продвинуться в этом направлении, так что начинать здесь не обязательно хорошо, поскольку это означает, что впереди, вероятно, много работы. Важно помнить, что это также масштабируемая графика; это означает, что мы можем ожидать худшую производительность, чем аналогичные библиотеки с растеризованным выводом, без использования каких-либо строк.
Несмотря на проблемы и некоторые улучшения, которые определенно можно было бы внести, я очень доволен тем, как все получилось, и с нетерпением жду добавления дополнительных функций и доработки оптимизации для этого пакета.