¿Cuál es el algoritmo correcto para una curva de distribución logarítmica entre dos puntos?

StackOverflow https://stackoverflow.com/questions/604953

  •  03-07-2019
  •  | 
  •  

Pregunta

He leído un montón de tutoriales sobre la forma correcta de generar una distribución logarítmica de pesos de tagcloud. La mayoría de ellos agrupan las etiquetas en pasos. Esto me parece un tanto tonto, así que desarrollé mi propio algoritmo basado en lo que he leído para que distribuya dinámicamente el recuento de la etiqueta a lo largo de la curva logarítmica entre el umbral y el máximo. Aquí está la esencia en 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)
    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

Básicamente, sin el cálculo logarítmico del recuento individual, generaría una línea recta entre los puntos, (mincount, minsize) y (maxcount, maxsize).

El algoritmo realiza una buena aproximación de la curva entre los dos puntos, pero tiene un inconveniente. El mincount es un caso especial, y su logaritmo produce cero. Esto significa que el tamaño de la cuenta mínima sería menor que el tamaño mínimo. He intentado inventar números para tratar de resolver este caso especial, pero parece que no puedo hacerlo bien. Actualmente solo trato el mincount como un caso especial y agrego & Quot; or 1 & Quot; a la línea de logcount.

¿Existe un algoritmo más correcto para dibujar una curva entre los dos puntos?

Actualización 3 de marzo : si no me equivoco, tomo el registro del recuento y luego lo conecto a una ecuación lineal. Para poner la descripción del caso especial en otras palabras, en y = lnx en x = 1, y = 0. Esto es lo que sucede en el mincount. Pero la cuenta mínima no puede ser cero, la etiqueta no se ha usado 0 veces.

Pruebe el código y conecte sus propios números para probar. Considero que tratar la cuenta mínima como un caso especial está bien, tengo la sensación de que sería más fácil que cualquiera que sea la solución real a este problema. Siento que debe haber una solución para esto y que alguien probablemente ha encontrado una solución.

ACTUALIZACIÓN 6 de abril : Un simple google la búsqueda muestra muchos de los tutoriales que he leído, pero esto probablemente sea el ejemplo más completo de nubes de etiquetas escalonadas.

ACTUALIZACIÓN 28 de abril : en respuesta a la solución de antti.huima: cuando se grafica, la curva que crea su algoritmo se encuentra debajo de la línea entre los dos puntos. He estado tratando de hacer malabares con los números, pero todavía no puedo encontrar una manera de voltear esa curva al otro lado de la línea. Supongo que si la función se cambiara a alguna forma de logaritmo en lugar de un exponente, haría exactamente lo que necesitaría. ¿Es eso correcto? Si es así, ¿alguien puede explicar cómo lograr esto?

¿Fue útil?

Solución

Gracias a la ayuda de antti.huima, volví a pensar lo que estaba tratando de hacer.

Tomando su método para resolver el problema, quiero una ecuación en la que el logaritmo del mincount sea igual a la ecuación lineal entre los dos puntos.

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

Si bien esto me da un buen punto de partida, necesito que pase por el punto (MAX, max_weight). Necesitará una constante:

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

Resolviendo para K obtenemos:

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

Entonces, para volver a poner todo esto en algún código de 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

Otros consejos

Comencemos con su mapeo desde el recuento registrado hasta el tamaño. Ese es el mapeo lineal que mencionaste:

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

donde min y max son los tamaños min y max , y a = log (maxcount) -b. La línea es de y = mx + c donde x = log (cuenta) -b

En el gráfico, podemos ver que el gradiente, m, es (maxsize-minsize) / a.

Necesitamos x = 0 en y = minsize, entonces log (mincount) -b = 0 - > b = log (mincount)

Esto nos deja con la siguiente python:

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

Si desea asegurarse de que el recuento mínimo sea siempre al menos 1, reemplace la primera línea con:

mincount = min(count+[1])

que agrega 1 a la lista de conteo antes de hacer el min. Lo mismo ocurre para asegurarse de que la cuenta máxima siempre sea al menos 1. Por lo tanto, su código final según lo anterior es:

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

lo que tiene es que tiene etiquetas cuyos recuentos van de MIN a MAX; el problema del umbral puede ignorarse aquí porque equivale a establecer cada recuento por debajo del umbral en el valor del umbral y tomar el mínimo y el máximo solo después.

Desea asignar los recuentos de etiquetas a " pesos " pero de una manera & "; logarítmica &"; que básicamente significa (según tengo entendido) lo siguiente. Primero, las etiquetas con el conteo MAX obtienen el peso máximo_peso (en su ejemplo, 1.75):

weight(MAX) = max_weight

En segundo lugar, las etiquetas con el recuento MIN obtienen min_weight weight (en su ejemplo, 0.75):

weight(MIN) = min_weight

Finalmente, sostiene que cuando su conteo disminuye en 1, el peso se multiplica por una constante K < 1, que indica la inclinación de la curva:

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

Resolviendo esto, obtenemos:

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

Tenga en cuenta que con x = MAX, el exponente es cero y el multiplicando de la derecha se convierte en 1.

Ahora tenemos el requisito adicional de que weight (MIN) = min_weight, y podemos resolver:

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

de donde obtenemos

K ^ (MAX - MIN) = weight_min / weight_max

y tomando logaritmo en ambos lados

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

es decir

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

El lado derecho es negativo como se desea, porque K < 1. Entonces

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

Entonces, ahora tiene la fórmula para calcular K. Después de esto, simplemente solicita cualquier conteo x entre MIN y MAX:

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

Y ya está.

En una escala logarítmica, simplemente grafica el registro de los números linealmente (en otras palabras, finge que estás trazando linealmente, pero toma el registro de los números que se trazarán primero).

El problema de cero no puede resolverse analíticamente: debe elegir un orden de magnitud mínimo para su escala, y sin importar lo que sea, nunca puede llegar a cero. Si desea trazar algo en cero, sus opciones son darle arbitrariamente el orden mínimo de magnitud de la escala, u omitirlo.

No tengo la respuesta exacta, pero creo que desea buscar Linealizar datos exponenciales. Comience calculando la ecuación de la línea que pasa por los puntos y tome el registro de ambos lados de esa ecuación.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top