Логарифмическая шкала как построить
Перейти к содержимому

Логарифмическая шкала как построить

Сравнение равномерной, логарифмической и степенной шкал

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

Равномерная шкала

Самый распространенный и привычный вид шкал. Также их называют арифметическими или линейными шкалами. На такой шкале значения равноудалены друг друг от друга.
Например значения 100 и 200, и 200 и 300 отстают друг от друга на одно и тоже расстояние.
Например, на этом графике по оси Y — равномерная шкала с шагом в 20 лет средней продолжительности жизни, а по оси X — равномерная шкала с шагом 10 календарных лет.

Логарифмическая шкала

Этот вид шкал тоже используется достаточно часто, особенно когда речь идёт о научных исследованиях. Она используется для отображения широко диапазона величин, когда значения, которые попадают на график отличаются на много порядков. То есть когда мы хотим одновременно видеть и значения 0.1, 0.2 и значения 100, 200 на одном графике. Зачастую это связанно с физикой процесса. Так, например, в музыке ноты, различающиеся по частоте в два раза это ноты на октаву выше (Ля и Ля следующей октавы). Чтобы показать частоты двух нот будет удобно использовать логарифмическую шкалу.

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

Чаще всего используются логарифмические шкалы с основанием 10. Это значит, что одинаковые расстояние на графике откладываются между значениями отличающимися на один порядок. Но бывают логарифмические шкалы с другими основаниями. Например 2.

Степенная шкала

Это менее известный тип шкал. Он отличается от остальных тем, что расстояние между рисками, соответствует числам возведенным в степень. То есть получается, что расстояние между соседними рисками постоянно растёт или уменьшается. Такие шкалы удобны, когда мы хотим показать на одном графике более детально какую-то группу значений, но при это не хотим потерять из вида, значения которые, сильно отличаются от этой группы. Чем-то это похоже на логарифмическую шкалу, но здесь идёт акцент не на всем промежутке, а только на отдельной его части. Это хорошо видно на примере РИА новости, где они использовали степенные шкалы, чтобы сгладить выбросы по доходам отдельных депутатов.

Со степенной шкалой

С равномерной шкалой

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

Сравнение шкал

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

Интерполяция + (линейная | логарифмическая) шкала + С++

Понадобилось мне как-то сделать интерфейс для загрузки в микроконтроллер график функции «сопротивление -> температура» (график решили задавать по нескольким точкам, а потом их интерполировать). По ходу дела выяснилось, что график будет оч-чень нелинейным (180 Ом -> 100 o , 6 000 Ом -> 0 o , 30 000 Ом -> -30 o ). Поэтому пришлось мне погрузиться в тему логарифмических шкал… и сразу вынырнуть, так как я не нашел того, что мне нужно. А нужно-то мне было всего лишь понять математику (и реализацию на С++) таких дел. ЧуднО — вроде такая нужная тема, а не расписано! Ну да ладно — мозги заскрипели и вспомнили высшую математику из университета, и программа была написана. Решил описать я свои мытарства тут — может, кому пригодится.

В этой статье я распишу теорию (а также базовые виртуальные классы), в следующей возьмусь за конкретные реализации средствами Qt.

Осторожно: в тексте много графики!

Откуда растут ноги у задачи

В общем, надо мне сделать регулятор холостого хода — такая штука для автомобиля, которая при холостом ходе в зависимости от температуры двигателя должна поддерживать определенные обороты. Поддерживает она их, регулируя заслонку шаговым двигателем.

В общем, мне надо знать текущую температуру. Было решено измерять ее штатными средствами — с терморезистора. Измеряем падение напряжения на нем — получаем сопротивление. Далее из таблицы (поскольку это микроконтроллер) получаем требуемые обороты.

Таблицу эту надо задать (для этого программа пишется средствами Qt). У меня есть несколько точек «сопротивление => температура». Мне надо для каждого кода АЦП (для ряда значений сорпотивлений) получить соответствующую температуру. Поскольку у разных автомобилей эти значения могут быть разными, то надо на экране, сверившись с таблицей, задать несколько точек на кривой.

По ходу дела оказалось, что график этот будет явно в логарифмическом масштабе. Значит, надо его вывести на экран. Как это сделать — читаем дальше.

Постановка задачи

Давайте немного подробнее опишем что нам надо:

  1. задание функции — необходимо задать несколько точек, по которым строится график. Вобщем, вспоминаем интерполяцию;
  2. построение графика функции — да-да, я знаю про Qwt. Может, не очень хорошо я его знаю, т. к. я не нашел в нем следующей возможности:
  3. интерактивное задание функции — мне нужно двигать точки, по которым строится функция, прямо на экране, в текущих экранных координатах шкалы, которые переводятся в реальное значение;
  4. линейная/логарифмическая шкала — поскольку значения вот такие, как я написал, мне пришлось заложить возможность менять шкалу. Причем как одну, так и обе сразу.

Вот такое ТЗ… Ну да ничего, я справился! Давайте и вам помогу.

Да, пока не нырнули — спасибо Equation Editor-у от CodeCogs! С их помощью я лихо построил все математические формулы без всяких Microsoft Equation Editor, которые потом надо еще экспортировать в графику со вставкой сюда. Кстати, там есть и русский редактор. В общем, рекомендую!

Ну и если вместо формул вы видите пустые квадратики — это тоже «спасибо» Equation Editor-у…

Прикрепленный Excel-файл

По ходу написания статьи я все расчеты строил и проверял в таблице Excel с формулами. Оказалось очень удобно. И я решил его выложить для общественного пользования. Там внизу перечислены страницы по разделам. На каждой странице параметры, которые можно менять, отмечены как ячейки с желтым фоном. Остальные клетки лучше не трогать. Впрочем, все формулы можно смело смотреть. Скачивайте файлик и пробуйте на здоровье! Если проблемы с файлом — пишите, вышлю.

Функциональная зависимость

Итак, у нас есть некоторая зависимость — обозначим ее как image. Здесь у нас image— горизонтальная ось графика, image— вертикальная. В моем случае imageбыло значение сопротивления, image— температура.

Почему не image? Ведь вроде бы должно быть так? Так-то оно так, но только в школе в простейшем случае.

image— это координаты точки на плоскости. Для простоты определимся использовать Декартову систему координат: imageзадает вертикальное смещение горизонтальной оси относительно нуля, imageзадает горизонтальное смещение вертикальной оси относительно нуля.

Все хорошо тогда, когда мы рисуем на бумаге эту самую систему координат и в ней ставим точки. Там и вправду — выбрали центр, линейкой отложили сюда, потом туда. А вот при построении графика в какой-то программе уже тонкости начинаются — что считать нулем? Что считать за «+», а что за «-«? Я рисую для этой статьи графику в CorelDRAW — там центр считается снизу слева (его можно передвинуть куда надо).

Да и в каких единицах график-то? В сантиметрах? А почему? У меня следующий этап будет реализация на С++ средствами Qt, так там я сделаю окно QWidget, у которого по умолчанию ноль — это слева сверху; единицы измерения — экранные пиксели.

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

Но это только лишь точка. А у нас будет какая-то линия, точнее — много линий. Что там будут за преобразования?

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

Итак, давайте договоримся о следующем: у нас есть некоторый абстрактный процесс, который описывается функциональной зависимостью image. При отображении на экран используется преобразование в координаты image, где image, image. Следующие шаги — это прояснить эти самые imageи image.

Но отложим пока в сторону координаты — нам надо как-то задать нашу функцию (помните ТЗ)? Причем задать в тех самых абстрактных координатах image. Этим и займемся.

Интерполяция

В моем случае был известен ряд точек image:

image, Ω image, ˚
180 100
6 000 0
30 000 -30

Не ахти какая сложна и большая таблица, но тут явно куча пустых мест. А какое сопротивление соответствует 60˚, -40˚, . В общем, надо проставить отсутствующие точки. И в этом нам помогут интерполяция, аппроксимация и экстраполяция. Впрочем, не пугайтесь — одной интерполяции хватит за глаза.

imageМетодов интерполяции много, все рассматривать я тут не буду. Лично мне приглянулся вначале интерполяционный многочлен Лагранжа. Он весьма прост в расчете и реализации, а также в настройке. Там предполагается, что задано множество из imageточек вида (тут мы на время таки вернемся к заданию точек в виде image— так уж принято в математике).

Многочлен вычисляется как , где .

Математика испугала? Хм… Ладно, напишу на языке С++:

Как видите, все достаточно тривиально (насколько тривиальными могут быть полиномы).

Еще одно большое достоинство полиномов Лагранжа — их легко можно промоделировать в таблице Excel-я, что я и делал.

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

Работая в Corel, я был близко знаком с кривыми Безье — тоже достаточно удобное и простое представление табличных данных. Весьма легко реализуется в программировании. Однако это уже не интерполяция, а, скорее, аппроксимация, т. к. тут приходится подгонять кривую к нужному виду.

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

Говоря языком математики, мы между точками и проводим прямые линии вида .

Опять же, на языке С++ это будет выглядеть так:

Тоже ничего революционного, не так ли?

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

Впрочем, не будем заморачиваться сейчас на методах интерполяции. Давайте лучше мы сделаем базовый класс, от которого будем наследовать реализации различных методов $#*@!поляции.

Базовый класс для задания/расчета функции

Что этот класс должен уметь делать? Мне кажется, что такой класс должен:

  • давать значение функции в зависимости от аргумента — собственно, ради чего мы его и городим;
  • реагировать (перемещать и пересчитывать) на изменение точек интерполяции — на вход подается факт нажатия/отпускания в некоторой координате, в результате чего производится пересчет параметров;
  • различать одиночный и двойной щелчок мышки — одиночное нажатие, как по мне, указывает на передвижение точки; двойное создает новую точку;
  • прорисовывать точки интерполяции как при их движении, так и без оного — т. к. у разных методов интерполяции будет разное интуитивно понятное значение точек интерполяции, то и выводить их должен производный класс (например, в интерполяции точка является частью графика; в аппроксимации точка не обязательно лежит на графике; в кривых Безье часть точек лежит на графике, часть задают форму);
  • давать координаты текущей передвигаемой точки — это нужно для вывода текста координаты данной точки;
  • давать сервисную информацию — например, «определена ли функция?», «сколько точек для интерполяции используется?», «получить координаты точек» и т. п. Эти данные позволят сохранить текущие настройки;
  • производить настройку — «распределить столько-то точек», «задать координаты точки» — это нам позволит восстановить сохраненные настройки.

Еще есть мысли? Если будут — пишите в комментариях, добавим!

Получается такой вот класс:

(Тем, кто недоволен моим стилем и структурой — предложите объективно лучше!)
(Тем, кто найдет ошибки в коде — спасибо!)

Думаю, тут все очевидно.

Для координат используется представление точки в виде QPointF (пара чисел в виде qreal, qreal. «На всех платформах, кроме ARM, используется double» — так написано для Qt 4.8).

Нажатия на кнопки мышки реализованы функциями MouseClicked , MouseDblClicked , MouseReleased и MouseMove . Предполагается, что в конкретных реализациях будут соответствующие реакции.

Для прорисовки точек используются методы DrawPoints и DrawCurPoint . Если для всех методов, кроме этих, координаты используются абстрактные, то тут нужны самые что ни на есть экранные. Поэтому сюда передаются два объекта класса ScaleBase для преобразований. Этот класс — тоже виртуальный. Его предки реализуют преобразование из абстрактных координат в текущие экранные. Сам же этот класс будет описан ниже.

Текущее значение функции возвращает метод f (const qreal) и перегруженная операторная функция operator() (const qreal) .

Для задания структуры используются функции set_points (Num) — задание количества точек, point (Num) , set_point (Num) , get_point (Num) — задание координат конкретной точки. num_points () const — возвращает количество точек, point (Num) const , get_point (Num) const возвращают координаты точки. is_specified () const возвращает true , если структура функции задана.

В следующей статье мы распишем пару вариантов реализации этого класса.

Функция преобразования для вертикальной/горизонтальной шкалы

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

Вариант первый — обе шкалы линейные. Вариант второй — обе логарифмические. Варианты третий и четвертый — смешанные графики. Кстати, в моем случае именно смешанный случай в итоге и подошел, т. к. по горизонтали у меня потребовался логарифмический масштаб, по вертикали — линейный.

Следовательно, задачу отображения нужно решать отдельно для обеих осей.

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

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

  • пределы абстрактных координат — те предельные значения аргумента и функции, что нас интересуют. Это будут image, imageдля горизонтальной оси и image, imageдля вертикальной. Не нужно допускать классическую ошибку: image, image! В приведенном примере это проиллюстрировано;
  • пределы экранных координат — границы картинки, в которой рисуется график. Естественно, в текущих экранных координатах. На графике это image, imageдля горизонтальной оси и image, imageдля вертикальной;
  • шаг экранной координаты — текущий шаг пикселя image, image. В простом случае это будет единица. Но в Qt вертикальный ноль — это верх окна. Следовательно, image. А еще могут быть применены всякие трансформации, и шаг уже будет отнюдь не единичным.

Обратите внимание — для задачи преобразования не важно, вертикальная это ось или нет! Она (задача) оперирует граничными значения входного, выходного, а также шагом выходного параметров. Следовательно, задачу можно обобщить: необходимо преобразовать параметр на основе его пределов , в выходную величину с учетом ее пределов , и шага . Тут намеренно введены обозначения , вместо привычных alt=»image» />, alt=»image» />, т. к. иначе будет путаница. Одно существенное дополнение: .

Базовый класс для преобразований шкал

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

  • преобразований из экранных координат в абстрактные и наоборот — логично, ради этого мы его и делаем;
  • настройка преобразования — это тоже логично;
  • свойства — текущие свойства преобразования (минимальные/максимальные значения, шаг для разных значений);
  • информация по сетке — позиции для крупной и мелкой сетки, подписи под шкалой.

Реализация может выглядеть так:

Настройка шкалы производится функциями set_. (Val) . Пересчет необходимых значений должен производится в этих же функциях. При изменении размера окна вызывается метод Resized (Size) .

Для повышения производительности можно один раз рассчитать соответствие точки на экране и ее значение в исходных, абстрактных координатах. Этот массив возвращается методом scr_values () const . Далее, для построения крупной и мелкой сетки рассчитываются массивы (возвращают их, соответственно, функции scr_maj_grid () и scr_min_grid () ). Длина массива соответствует количеству оных линий, значение — смещению относительно начала шкалы на экране (т. е. индексу первого массива). Также заранее рассчитываются два массива — текст подписи к шкале (функция scr_text_str () ) и смещения этих подписей относительно начала (функция scr_text_pos () ).

Наконец, прямое преобразование — из абстрактных в экранные координаты — производится функцией scr (Val) , обратное — функцией val (Scr) .

Линейное преобразование

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

Мы имеем некоторую функцию — кривую в одном представлении. Для другого нам пришлось ее сузить и сместить вправо (окно на экране уменьшили и сдвинули вправо). Для другого представления нам пришлось сместить ее влево (окно сдвинули влево). Как это описывается математически? Достаточно просто: alt=»image» />. В первом случае, похоже, , во втором alt=»image» />.

В другом случае нам пришлось сузить вертикальное представление кривой и сдвинуть вверх. А потом и вообще — перевернуть. Оба этих преобразования описываются как alt=»image» />. В первом случае , . Во втором случае alt=»image» />.

Оба преобразования имеют одно и то же математическое описание: . В данном описании есть две константы, которые определяют преобразование — alt=»image» />и alt=»image» />. Первая определяет угол наклона, вторая — смещение относительно нуля.

Расчет этих констант достаточно прост — это решение системы двух уравнений:

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

Шаг в данном случае для расчета не используется, но он потом нам пригодится в реализации на С++ для расчета смещения.

Как это будет использоваться на практике? Да все просто! Горизонтальное преобразование: — граница картинки графика, соответствующая (как правило, слева), — (как правило, справа), — шаг вывода картинки по горизонтали. Вертикальное преобразование — аналогично, но по вертикали (у нас в Qt будет нижней границей картинки, — верхней, причем ).

Логарифмическое преобразование

А теперь окунемся туда, ради чего все это закрутилось:

(на графике не логарифм нарисован, а что-то похожее на него. Сделано это специально, т. к. логарифм тут будет не очень нагляден)

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

Всего точек у нас будет (например, , , ; тогда у нас будет 3 точки). Этому соответствует диапазон входных значений . Значению соответствует , соответствует . Последняя точка имеет индекс . Чему соответствует ?

Для линейной шкалы , где определяется диапазоном входных значений. Тут можно поступить аналогично, только вместо умножения будет возведение в степень: (помним, что ). Есть один, правда, ньюанс: при у нас , а должно бы. Это решается просто — вычтем единицу: . И тогда для нулевого случая все сходится. Как рассчитать в данном случае? Есть разные варианты. Я это предпочитаю сделать следующим образом.

Нам известно, что . В то же время мы теперь знаем, что . Получается уравнение: . Решим его относительно : . Вспомнив, чему равно и заменив корень возведением в степень, получим приемлемый для компьютера вид: (напоминаю, что ). Прекрасно, базовая величина получена!

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

Для нахождения надо решить уравнение относительно : . Ну а дальше уже из свойств логарифма получим, что . Ну и далее, рассматривая экранные координаты как линию с наклоном, получим, что (для полной красоты надо еще заменить на ).

Вроде бы базовую математику рассмотрели. Нашли ошибки или неточности — пишите в комментариях, буду благодарен!

Со временем напишу следующую статью — реализацию этой математики средствами Qt языка C++.

Какую шкалу графика выбрать

При подготовке инструкции по работе с графиками на сайте Stockcharts.com я не уточнила такой важной момент, как выбор шкалы. Уточняю. Если вы посмотрите на график движения цены, то увидите на нем две оси. По горизонтальной оси X отложено время, по вертикальной оси Y – цена.

Для оси X используется арифметическая (линейная) шкала. Для оси Y может применяться как арифметическая, так и логарифмическая шкала. В чем их отличие?

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

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

Принцип построения арифметической шкалы

Арифметическая шкала строится от нуля и с помощью сложения и вычитания. При перемещении вправо от нуля значения складываются, а при перемещении влево от нуля – вычитаются. Значение показателя по мере продвижения оси изменяется на равную величину. В результате отрезок шкалы 10-20 будет равен отрезку 100-110, несмотря на то, что в первом варианте изменение составит 100% (2 раза), а во втором – лишь 10%.

Принцип построения логарифмической шкалы

В логарифмической шкале равным отрезкам соответствует одинаковое процентное изменение показателя. Например, отрезок шкалы 10-20 будет равен отрезкам 20-40 и 40-80, т.е. изменение показателя во всех случаях составит 2 раза. Данная шкала строится от единицы с помощью умножения и деления. И для того чтобы переместиться на такое же расстояние как в арифметической шкале, например, на 10, необходимо 1 умножить на 10. В свою очередь, для перемещения на 10 влево, нужно 1 разделить на 10.

Отображение графиков с разной шкалой

При работе с графиками на коротких интервалах времени разница между арифметическим и логарифмическим способами шкалирования едва заметна. Однако при больших ценовых колебаниях и длительных периодах различия существенны. Эта хорошо видно на рисунке ниже. На нем я наложила графики индекса NYSE Composite ($NYA) за более чем 20-летний период. Менее четкая линия – это линия графика, построенного по логарифмической шкале.

nyse_log-vs-line-scale

График индекса NYSE Composite ($NYA), построенный по арифметической и логарифмической шкале

Живая версия графиков доступна здесь и здесь.

Обратите внимание, что диапазон колебаний на графике с арифметической шкалой выше, чем с логарифмической. Кроме этого, на графике с арифметической шкалой есть ощущение более существенного движения и изменения. Поэтому такой способ построения любят СМИ: он позволяет преувеличить значения показателей и раздуть новость. Этого не происходит, когда данные отображаются на логарифмической шкале. И именно ее я рекомендую использовать при работе с графиками, охватывающими данные за длительный период.

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

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