Reddit: ¿qué sistema usa para puntuar las entradas?

Para los que no lo sepan, Reddit es un proyecto de código abierto, escrito en Python, cuyo funcionamiento se basa en usuarios que publican historias, a las que el resto de usuarios le otorgan una puntuación positiva o negativa y en la que pueden participar añadiendole comentarios. Hace unos meses, Amir Salihefendic, cofundador del popular Plurk, se tomó la molestia de revisar el código fuente de Reddit para estudiar el modo en que este popularísimo portal puntúa y construye la página de las historias enviadas por los usuarios.

Dicho artículo se extendió como la pólvora ya que daba una explicación muy clara, exponiendo trozos de código y fórmulas matemáticas muy bien documentadas. Aunque el sistema de ranking se halla escrito en lenguaje Pyrex (sirve para escribir extensiones de Python) Amir Salihefendic los ha traducido a Python para que se puedan comprender mejor.

Una de las cosas que se dio cuenta es que este sistema de ranking se basa en la frescura de un artículo, frescura que se desintegra con una caducidad casi calculada. Aquí lo ilustra en este gráfico obtenido de su web:

Ante todo observad la escala del gráfico para no llevaros a equívocos. El código que hace esta función es este:

 #Rewritten code from /r2/r2/lib/db/_sorts.pyx
 from datetime import datetime, timedelta
 
 from math import log
 
 epoch = datetime(1970, 1, 1)
 
 def epoch_seconds(date):
     """Returns the number of seconds from the epoch to date."""
     td = date - epoch
     return td.days * 86400 + td.seconds + (float(td.microseconds) / 1000000)
 
 def score(ups, downs):
     return ups - downs
 
 def hot(ups, downs, date):
     """The hot formula. Should match the equivalent function in postgres."""
     s = score(ups, downs)
     order = log(max(abs(s), 1), 10)
     sign = 1 if s > 0 else -1 if s < 0 else 0
     seconds = epoch_seconds(date) - 1134028003
     return round(order + sign * seconds / 45000, 7)

Pero esta puntuación y posición en el ranking no sólo lo aporta la fecha de envío sino que es necesaria la acción de la comunidad de Reddit para promocionar entradas usando los botones de votaciones, voto positivo o negativo. Es una fórmula no linear, logarítmica y el impulso obtenido en las primeras votaciones es mucho mayor que en las últimas. En realidad los primeros 10 votos proporcionan la misma adición de puntos que los siguientes 100:

El uso de votos negativos también tiene su influencia pero, al contrario que en otros sistemas de este tipo, no penalizan en mayor medida sino que simplemente se restan a los positivos. Unido a todo esto el sistema anterior resulta que los temas con muchos votos positivos y negativos puntúan realmente alto favoreciendo así que los temas controvertidos (con la balanza ligeramente inclinada hacia alguna parte) terminen en portada. Los comentarios cumplen casi todo este sistema excepto que no "caducan" en el tiempo, no se les penaliza por la poca frescura.

Y aquí entra en escena possiblywrong, donde hace unos días analizó en detalle un problema que surgen de los mismos comentarios. He aquí el código y la correspondiente fórmula (Wilson score interval) que pretende representar:

        # From /r2/r2/lib/db/_sorts.pyx
    from math import sqrt
     
    def confidence(ups, downs):
        n = ups + downs
        if n == 0:
            return 0
        z = 1.0 #1.0 = 85%, 1.6 = 95%
        phat = float(ups) / n
        return sqrt(phat+z*z/(2*n)-z*((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)

Lo primero de lo que se dió cuenta fue de que la raiz cuadrada está en el sitio equivocado, ¡la fórmula está mal implementada! Cualquier cosa que Reddit esté calculando no es la fórmula de Wilson que tenían como objetivo. El segundo error está en el doble intervalo que representa la fórmula, que tampoco está bien representado en el código, lo que junto con el primer error produce el error de que esta función siempre devuelve valores positivos, incluso para comentarios que no hayan recibido ni un sólo voto positivo. De esta manera possiblywrong ofrece una versión corregida de la implementación, donde hace bien la raiz cuadrada, define bien el rango y arregla el bug de los comentarios.

Más información | Artículo de Amir Salihefendic Más información | Artículo de PossibleWrong

Portada de Genbeta