Каков правильный алгоритм для кривой логарифмического распределения между двумя точками?

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

from math import log
count = [1, 3, 5, 4, 7, 5, 10, 6]
def logdist(count, threshold=0, maxsize=1.75, minsize=.75):
    countdist = []
    # mincount is either the threshold or the minimum if it's over the threshold
    mincount = threshold<min(count) and min(count) or threshold
    maxcount = max(count)
    spread = maxcount - mincount
    # the slope of the line (rise over run) between (mincount, minsize) and ( maxcount, maxsize)
    delta = (maxsize - minsize) / float(spread)
    for c in count:
        logcount = log(c - (mincount - 1)) * (spread + 1) / log(spread + 1)
        size = delta * logcount - (delta - minsize)
        countdist.append({'count': c, 'size': round(size, 3)})
    return countdist

По сути, без логарифмического расчета индивидуального количества будет создана прямая линия между точками (mincount, minsize) и (maxcount, maxsize).

Алгоритм хорошо аппроксимирует кривую между двумя точками, но имеет один недостаток. Mincount — это особый случай, и его логарифм дает ноль. Это означает, что размер mincount будет меньше, чем minsize. Я пробовал придумывать числа, чтобы попытаться решить этот особый случай, но, похоже, не могу понять это правильно. В настоящее время я рассматриваю mincount как особый случай и добавляю «or 1» в строку logcount.

Есть ли более правильный алгоритм для рисования кривой между двумя точками?

Обновление от 3 марта: если я не ошибаюсь, я беру журнал подсчета, а затем подставляю его в линейное уравнение. Другими словами, если описать частный случай, то в y=lnx при x=1, y=0. Вот что происходит на минкаунте. Но mincount не может быть равен нулю, тэг не использовался 0 раз.

Попробуйте код и введите свои собственные числа для проверки. Отношение к mincount как к частному случаю меня вполне устраивает, мне кажется, это будет проще, чем какое бы то ни было реальное решение этой проблемы. Я просто чувствую, что должно быть решение этой проблемы и что кто-то, вероятно, придумал решение.

ОБНОВЛЕНИЕ от 6 апреля: простое google поиск выдает множество руководств, которые я читал, но это, вероятно, наиболее полное пример ступенчатого облака тегов.

ОБНОВЛЕНИЕ 28 апр. В ответ на решение antti.huima: на графике кривая, созданная вашим алгоритмом, лежит ниже линии между двумя точками. Я пытался жонглировать числами, но до сих пор не могу придумать способ перевернуть эту кривую на другую сторону линии. Я предполагаю, что если бы функция была изменена на некоторую форму логарифма вместо экспоненты, она сделала бы именно то, что мне нужно. Это правильно? Если да, то может ли кто-нибудь объяснить, как этого добиться?


person dburke    schedule 03.03.2009    source источник
comment
Вы упомянули учебники, я могу получить ссылки?   -  person akuhn    schedule 23.03.2009
comment
согласен, без дополнительной информации довольно сложно понять, в чем проблема.   -  person wds    schedule 24.03.2009


Ответы (5)


Благодаря помощи antti.huima я переосмыслил то, что пытался сделать.

Принимая его метод решения задачи, я хочу уравнение, в котором логарифм mincount равен линейному уравнению между двумя точками.

weight(MIN) = ln(MIN-(MIN-1)) + min_weight
min_weight = ln(1) + min_weight

Хотя это дает мне хорошую отправную точку, мне нужно, чтобы она прошла через точку (MAX, max_weight). Ему понадобится константа:

weight(x) = ln(x-(MIN-1))/K + min_weight

Решая K, получаем:

K = ln(MAX-(MIN-1))/(max_weight - min_weight)

Итак, чтобы поместить все это обратно в некоторый код Python:

from math import log
count = [1, 3, 5, 4, 7, 5, 10, 6]
def logdist(count, threshold=0, maxsize=1.75, minsize=.75):
    countdist = []
    # mincount is either the threshold or the minimum if it's over the threshold
    mincount = threshold<min(count) and min(count) or threshold
    maxcount = max(count)
    constant = log(maxcount - (mincount - 1)) / (maxsize - minsize)
    for c in count:
        size = log(c - (mincount - 1)) / constant + minsize
        countdist.append({'count': c, 'size': round(size, 3)})
    return countdist
person dburke    schedule 30.04.2009

Давайте начнем с сопоставления зарегистрированного количества с размером. Это линейное отображение, о котором вы упомянули:

   size
    |
max |_____
    |   /
    |  /|
    | / |
min |/  |
    |   |
   /|   |
0 /_|___|____
    0   a

где min и max — это минимальный и максимальный размеры, а a=log(maxcount)-b. Строка имеет вид y=mx+c, где x=log(count)-b

Из графика видно, что градиент m равен (maxsize-minsize)/a.

Нам нужно x=0 при y=minsize, поэтому log(mincount)-b=0 -> b=log(mincount)

Это оставляет нас со следующим питоном:

mincount = min(count)
maxcount = max(count)
xoffset = log(mincount)
gradient = (maxsize-minsize)/(log(maxcount)-log(mincount))
for c in count:
    x = log(c)-xoffset
    size = gradient * x + minsize

Если вы хотите убедиться, что минимальное количество всегда равно как минимум 1, замените первую строку на:

mincount = min(count+[1])

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

from math import log
count = [1, 3, 5, 4, 7, 5, 10, 6]
def logdist(count, maxsize=1.75, minsize=.75):
    countdist = []
    mincount = min(count+[1])
    maxcount = max(count+[1])
    xoffset = log(mincount)
    gradient = (maxsize-minsize)/(log(maxcount)-log(mincount))
    for c in count:
        x = log(c)-xoffset
        size = gradient * x + minsize
        countdist.append({'count': c, 'size': round(size, 3)})
    return countdist
person Phil H    schedule 24.03.2009

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

Вы хотите сопоставить количество тегов с «весами», но «логарифмическим способом», что в основном означает (насколько я понимаю) следующее. Во-первых, теги со значением count MAX получают вес max_weight (в вашем примере 1,75):

weight(MAX) = max_weight

Во-вторых, теги со значением MIN получают вес min_weight (в вашем примере 0,75):

weight(MIN) = min_weight

Наконец, считается, что когда ваш счет уменьшается на 1, вес умножается на константу K ‹ 1, что указывает на крутизну кривой:

weight(x) = weight(x + 1) * K

Решая это, мы получаем:

weight(x) = weight_max * (K ^ (MAX - x))

Обратите внимание, что при x = MAX показатель степени равен нулю, а множимое справа становится равным 1.

Теперь у нас есть дополнительное требование, что вес (MIN) = min_weight, и мы можем решить:

weight_min = weight_max * (K ^ (MAX - MIN))

из которого мы получаем

K ^ (MAX - MIN) = weight_min / weight_max

и логарифмирование с обеих сторон

(MAX - MIN) ln K = ln weight_min - ln weight_max

i.e.

ln K = (ln weight_min - ln weight_max) / (MAX - MIN)

Правая часть отрицательна, как и требовалось, поскольку K ‹ 1. Тогда

K = exp((ln weight_min - ln weight_max) / (MAX - MIN))

Итак, теперь у вас есть формула для расчета K. После этого вы просто применяете любое количество x между MIN и MAX:

weight(x) = max_weight * (K ^ (MAX - x))

Готово.

person Antti Huima    schedule 27.03.2009
comment
Это очень близко к тому, что я хочу. Единственная проблема заключается в том, что кривая находится не на той стороне линейного наклона. Вы предполагаете, что K должно быть меньше 1. Я бы хотел, чтобы он был немного больше 1. Как этого добиться? - person dburke; 07.04.2009
comment
Ах да, извините, вы правы --- в последнем уравнении измените MAX - x на x - MIN, а в предыдущем поменяйте местами ln weight_max и ln weight_min. - person Antti Huima; 08.04.2009
comment
На графике кривая, которую создает ваш алгоритм, лежит ниже линии между двумя точками. Я пытался жонглировать числами, но до сих пор не могу придумать способ перевернуть эту кривую на другую сторону линии. Я предполагаю, что если бы функция была изменена на некоторую форму логарифма вместо экспоненты, она сделала бы именно то, что мне нужно. Это правильно? - person dburke; 28.04.2009
comment
Привет, если вы просто не хотите переворачивать кривую, вам не нужно переходить с exp на log или наоборот. Вы можете переворачивать, просто вычитая и добавляя. Если вы хотите повернуть функцию f(x) на 180 градусов, она примет вид C - f(D - x), где D и C - некоторые константы, описывающие начало поворота; чтобы перевернуть его по оси Y, вы делаете f (D - x), а чтобы перевернуть по оси X C - f (x). - person Antti Huima; 29.04.2009
comment
Да, я знаю, я просто надеялся, что может быть какое-то более элегантное решение. Реальная проблема, которую я здесь решаю, технически даже не связана с графиками. Мой вопрос касается поиска наиболее элегантного решения довольно сложной математической задачи. - person dburke; 30.04.2009
comment
Кстати, я просто хотел поблагодарить вас за то, что вы были так полезны. Я надеюсь, что моя любознательная натура не производит впечатление требовательности или чего-то в этом роде. - person dburke; 30.04.2009

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

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

person Drew Hall    schedule 03.03.2009
comment
Если я правильно вас понимаю, я думаю, что я уже делаю это. Я беру журнал подсчетов и подставляю его в линейное уравнение. Я не уверен, что вы понимаете проблему особого случая. Я не пытаюсь найти значение в нуле, это значение в mincount равно 0. - person dburke; 03.03.2009

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

person John Ellinwood    schedule 03.03.2009