¿Cómo calcular el coeficiente de la ley de Zipf a partir de un conjunto de frecuencias máximas?

25

Tengo varias frecuencias de consulta y necesito estimar el coeficiente de la ley de Zipf. Estas son las frecuencias más altas:

26486
12053
5052
3033
2536
2391
1444
1220
1152
1039
Diegolo
fuente
De acuerdo con la página de Wikipedia, la ley de Zipf tiene dos parámetros. Número de elementos N y s el exponente. ¿Qué es N en tu caso, 10? ¿Y las frecuencias se pueden calcular dividiendo los valores suministrados por la suma de todos los valores suministrados?
mpiktas 01 de
deje que sea diez, y las frecuencias se pueden calcular dividiendo los valores suministrados por la suma de todos los valores suministrados ... ¿cómo puedo estimar?
Diegolo 01 de

Respuestas:

22

Actualización He actualizado el código con el estimador de máxima probabilidad según la sugerencia de @whuber. Reducir al mínimo la suma de los cuadrados de las diferencias entre las probabilidades teóricas del registro y las frecuencias del registro, aunque da una respuesta, sería un procedimiento estadístico si se pudiera demostrar que es algún tipo de estimador M. Lamentablemente, no pude pensar en ninguno que pudiera dar los mismos resultados.

Aquí está mi intento. Calculo los logaritmos de las frecuencias y trato de ajustarlos a los logaritmos de las probabilidades teóricas dadas por esta fórmula . El resultado final parece razonable. Aquí está mi código en R.

fr <- c(26486, 12053, 5052, 3033, 2536, 2391, 1444, 1220, 1152, 1039)

p <- fr/sum(fr)

lzipf <- function(s,N) -s*log(1:N)-log(sum(1/(1:N)^s))

opt.f <- function(s) sum((log(p)-lzipf(s,length(p)))^2)

opt <- optimize(opt.f,c(0.5,10))

> opt
$minimum
[1] 1.463946

$objective
[1] 0.1346248

El mejor ajuste cuadrático entonces es .s=1.47

La probabilidad máxima en R se puede realizar con la mlefunción (del stats4paquete), que calcula útilmente los errores estándar (si se proporciona la función de probabilidad máxima negativa correcta):

ll <- function(s) sum(fr*(s*log(1:10)+log(sum(1/(1:10)^s))))

fit <- mle(ll,start=list(s=1))

> summary(fit)
Maximum likelihood estimation

Call:
mle(minuslogl = ll, start = list(s = 1))

Coefficients:
  Estimate  Std. Error
s 1.451385 0.005715046

-2 log L: 188093.4 

Aquí está el gráfico del ajuste en la escala log-log (nuevamente como sugirió @whuber):

s.sq <- opt$minimum
s.ll <- coef(fit)

plot(1:10,p,log="xy")
lines(1:10,exp(lzipf(s.sq,10)),col=2)
lines(1:10,exp(lzipf(s.ll,10)),col=3)

La línea roja es la suma de los cuadrados ajustados, la línea verde es el ajuste de máxima probabilidad.

Gráfico de log-log de ajustes

mpiktas
fuente
1
También hay un paquete R zipfR cran.r-project.org/web/packages/zipfR/index.html No lo he probado.
parada
@onestop, gracias por el enlace. Sería bueno que alguien respondiera esta pregunta usando este paquete. Mi solución definitivamente carece de profundidad, aunque da algún tipo de respuesta.
mpiktas 01 de
(+1) Eres realmente impresionante. ¡Tantas buenas contribuciones en tantos campos estadísticos diferentes!
chl
@chl, gracias! Sin embargo, creo que no soy el único con tales características en este sitio;)
mpiktas
25

Hay varios problemas ante nosotros en cualquier problema de estimación:

  1. Estime el parámetro.

  2. Evaluar la calidad de esa estimación.

  3. Explore los datos.

  4. Evaluar el ajuste.

Para aquellos que usarían métodos estadísticos para la comprensión y la comunicación, el primero nunca debe hacerse sin los demás.

i=1,2,,nisss>0

Hs(n)=11s+12s++1ns.

i1n

log(Pr(i))=log(isHs(n))=slog(i)log(Hs(n)).

fi,i=1,2,,n

Pr(f1,f2,,fn)=Pr(1)f1Pr(2)f2Pr(n)fn.

Por lo tanto, la probabilidad de registro para los datos es

Λ(s)=si=1nfilog(i)(i=1nfi)log(Hs(n)).

s .

s^=1.45041Λ(s^)=94046.7s^ls=1.463946Λ(s^ls)=94049.5

ML también estimará s[1.43922,1.46162] (si hice los cálculos correctamente :-).

Dada la naturaleza de la ley de Zipf, la forma correcta de graficar este ajuste es en un gráfico log-log , donde el ajuste será lineal (por definición):

ingrese la descripción de la imagen aquí

Para evaluar la bondad del ajuste y explorar los datos, observe los residuos (datos / ajuste, ejes log-log nuevamente):

ingrese la descripción de la imagen aquí

Esto no es demasiado bueno: aunque no hay una correlación serial evidente o heteroscedasticidad en los residuos, generalmente son alrededor del 10% (lejos de 1.0). Con frecuencias de miles, no esperaríamos desviaciones en más de un pequeño porcentaje. losχ2=656.476


Debido a que los residuos parecen aleatorios, en algunas aplicaciones podríamos contentarnos con aceptar la Ley de Zipf (y nuestra estimación del parámetro) como una descripción aceptable aunque aproximada de las frecuencias . Sin embargo, este análisis muestra que sería un error suponer que esta estimación tiene algún valor explicativo o predictivo para el conjunto de datos examinado aquí.

whuber
fuente
1
@whuber, podría sugerir humildemente un poco de precaución con la formulación dada anteriormente. La ley de Zipf generalmente se establece como un resultado de frecuencia relativa. No es (normalmente considerado) la distribución de la que se extrae una muestra iid. Un marco iid probablemente no sea la mejor idea para estos datos. Quizás publique más sobre esto más tarde.
cardenal
3
@ cardinal Espero con interés lo que tienes que decir. Si no tiene tiempo para una respuesta exhaustiva, sería bienvenido incluso un bosquejo de lo que cree que podría ser la "mejor idea para estos datos". Puedo adivinar a dónde vas con esto: los datos se han clasificado, un proceso que crea dependencias y debería exigirme que defienda una probabilidad derivada sin reconocer los posibles efectos de la clasificación. Sería bueno ver un procedimiento de estimación con una justificación más sólida. Sin embargo, tengo la esperanza de que mi análisis pueda ser rescatado por el gran tamaño del conjunto de datos.
whuber
1
@cardinal, no hagas Fermat con nosotros :) Si tienes una visión diferente a la de otros respondedores, no dudes en expresarla en la respuesta por separado, incluso si no constituye una respuesta válida per se. En matemáticas, por ejemplo, estas situaciones surgen con bastante frecuencia.
mpiktas
1
@ Cardinal fácilmente. Por ejemplo, recolecta frecuencias e identifica y clasifica las diez más altas. Hipotetizas la Ley de Zipf. Recopila un nuevo conjunto de frecuencias y las informa de acuerdo con la clasificación anterior . Esa es la situación iid, a la que mi análisis se adapta perfectamente, dependiendo de que los nuevos rangos estén de acuerdo con los antiguos.
whuber
1
@whuber, gracias por tu paciencia. Ahora estoy completamente claro en su línea de razonamiento. Según el modelo de muestreo que ha desarrollado completamente, estoy de acuerdo con su análisis. Quizás su última declaración todavía es un poco resbaladiza. Si la clasificación no induce una fuerte dependencia, su método sería conservador. Si la dependencia inducida fuera moderadamente fuerte, podría volverse anticonservadora. Gracias por tu paciencia frente a mi pedantería.
cardenal
2

Las estimaciones de máxima verosimilitud son solo estimaciones puntuales del parámetro s . Se necesita un esfuerzo adicional para encontrar también el intervalo de confianza de la estimación. El problema es que este intervalo no es probabilístico. No se puede decir "el valor del parámetro s = ... tiene una probabilidad del 95% en el rango [...]".

Uno de los lenguajes de programación probabilísticos como PyMC3 hace que esta estimación sea relativamente sencilla. Otros idiomas incluyen Stan, que tiene excelentes características y una comunidad de apoyo.

Aquí está mi implementación de Python del modelo ajustado en los datos de los OP (también en Github ):

import theano.tensor as tt
import numpy as np
import pymc3 as pm
import matplotlib.pyplot as plt

data = np.array( [26486, 12053, 5052, 3033, 2536, 2391, 1444, 1220, 1152, 1039] )

N = len( data )

print( "Number of data points: %d" % N )

def build_model():
    with pm.Model() as model:
        # unsure about the prior...
        #s = pm.Normal( 's', mu=0.0, sd=100 )
        #s = pm.HalfNormal( 's', sd=10 )
        s = pm.Gamma('s', alpha=1, beta=10)

        def logp( f ):
            r = tt.arange( 1, N+1 )
            return -s * tt.sum( f * tt.log(r) ) - tt.sum( f ) * tt.log( tt.sum(tt.power(1.0/r,s)) )

        pm.DensityDist( 'obs', logp=logp, observed={'f': data} )

    return model


def run( n_samples=10000 ):
    model = build_model()
    with model:
        start = pm.find_MAP()
        step = pm.NUTS( scaling=start )
        trace = pm.sample( n_samples, step=step, start=start )

    pm.summary( trace )
    pm.traceplot( trace )
    pm.plot_posterior( trace, kde_plot=True )
    plt.show()

if __name__ == '__main__':
    run()

ss

enter image description here

Para proporcionar algunos diagnósticos básicos de muestreo, podemos ver que el muestreo estaba "mezclando bien" ya que no vemos ninguna estructura en la traza:

enter image description here

Para ejecutar el código, se necesita Python con los paquetes Theano y PyMC3 instalados.

¡Gracias a @ w-huber por su excelente respuesta y comentarios!

Vladislavs Dovgalecs
fuente
1

Aquí está mi intento de ajustar los datos, evaluar y explorar los resultados usando VGAM:

require("VGAM")

freq <- dzipf(1:100, N = 100, s = 1)*1000 #randomizing values
freq <- freq  + abs(rnorm(n=1,m=0, sd=100)) #adding noize

zdata <- data.frame(y = rank(-freq, ties.method = "first") , ofreq = freq)
fit = vglm(y ~ 1, zipf, zdata, trace = TRUE,weight = ofreq,crit = "coef")
summary(fit)

s <- (shat <- Coef(fit)) # the coefficient we've found
probs <- dzipf(zdata$y, N = length(freq), s = s) # expected values
chisq.test(zdata$ofreq, p = probs) 
plot(zdata$y,(zdata$ofreq),log="xy") #log log graph
lines(zdata$y, (probs)*sum(zdata$ofreq),  col="red") # red line, num of predicted frequency

enter image description here

    Chi-squared test for given probabilities

data:  zdata$ofreq
X-squared = 99.756, df = 99, p-value = 0.4598

En nuestro caso, las hipótesis nulas de Chi cuadrado son que los datos se distribuyen de acuerdo con la ley de zipf, por lo tanto, los valores p más grandes respaldan la afirmación de que los datos se distribuyen de acuerdo con ellos. Tenga en cuenta que incluso los valores p muy grandes no son una prueba, solo un indicador.

Guy s
fuente
0

x=1wx=1^

sUWSE^=H101(1wx=1^)

wx=1^=0.4695599775

sUWSE^=1.4

Nuevamente, el UWSE solo proporciona una estimación consistente, sin intervalos de confianza, y podemos ver cierta compensación en la precisión. La solución anterior de mpiktas también es una aplicación de UWSE, aunque se requiere programación. Para obtener una explicación completa del estimador, consulte: https://paradsp.wordpress.com/ - hasta el final.

CYP450
fuente
¿Cómo se relaciona UWSE con la ley de Zipf?
Michael R. Chernick
La UWSE (Estimación de espacio de peso único) utiliza el hecho de que la probabilidad / frecuencia más alta es única en diferentes valores del parámetro s, para un N dado, para encontrar s. Con respecto a la ley de Zipf, esto nos dice que, dado un número de elementos para clasificar, N y una frecuencia superior, solo hay una forma de asignar frecuencias a los elementos restantes (2, ..., N) de modo que podamos diga "el enésimo elemento es 1 / n ^ s veces más grande que el elemento más frecuente, para algunos s". En otras palabras, dada esta información, solo hay una forma de que la ley de Zipf se cumpla, por supuesto, suponiendo que la ley de Zipf se cumpla.
CYP450
0

Mi solución intenta ser complementaria a las respuestas proporcionadas por mpiktas y whuber haciendo una implementación en Python. Nuestras frecuencias y rangos x son:

freqs = np.asarray([26486, 12053, 5052, 3033, 2536, 2391, 1444, 1220, 1152, 1039])
x = np.asarray([1, 2, 3, 4, 5 ,6 ,7 ,8 ,9, 10])

Como nuestra función no está definida en todo el rango, debemos verificar que estamos normalizando cada vez que la calculamos. En el caso discreto, una aproximación simple es dividir por la suma de todos y (x). De esta forma podemos comparar diferentes parámetros.

f,ax = plt.subplots()
ax.plot(x, f1, 'o')
ax.set_xscale("log")
ax.set_yscale("log")

def loglik(b):  
    # Power law function
    Probabilities = x**(-b)

    # Normalized
    Probabilities = Probabilities/Probabilities.sum()

    # Log Likelihoood
    Lvector = np.log(Probabilities)

    # Multiply the vector by frequencies
    Lvector = np.log(Probabilities) * freqs

    # LL is the sum
    L = Lvector.sum()

    # We want to maximize LogLikelihood or minimize (-1)*LogLikelihood
    return(-L)

s_best = minimize(loglik, [2])
print(s_best)
ax.plot(x, freqs[0]*x**-s_best.x)

enter image description here

El resultado nos da una pendiente de 1.450408 como en las respuestas anteriores.

ivangtorre
fuente