Avalanche Effect

Homebrew of polygons, startups, and a dash of coding bits.

Ranking Popular Items With Newtonian Cooling

Having implemented the Reddit ranking algorithm and Wilson Score, I’ve recently been fascinated with Evan Miller’s article on Ranking Hotness with Newton’s Law of Cooling because of its simplicity and ease of implementation.

What I like most about exponential decay:

Minimize writes to the database

You only need to write to the database when you’re incrementing the temperature. No longer need to run Cron jobs to constantly update the db.

Scale with site usage

As your site grows, the algorithm will scale with usage. It’ll only need minor modifications to your parameters to surface New/Popular items to your liking.

Easy to add more signals that influence popularity

When incrementing the temperature, it’s easy to add more signals such as commenting/sharing activity into the equation.

Here’s my simple code implementation in Ruby/Rails:

Your migration rb file:

1
2
3
4
5
6
class AddLastTemperatureTimeToPosts < ActiveRecord::Migration
  def change
      ...
      add_column :posts, :last_temperature_at, :datetime
  end
end

In your Active Record Post class, two helper methods using the Ruby Math module:

1
2
3
4
5
6
7
8
9
10
class Post < ActiveRecord::Base
  ...
  def cooling_rate(lambda, hours)
    Math.exp(lambda * hours)
  end

  def last_reading
    (Time.now - self.last_temperature_at) / 3600.0
  end
end

To understand the implementation, let’s look at the general equation for exponential decay:

1
2
3
4
5
T = T0 * e ^ (-lambda * t)
Where:
T0 = last recorded temperature
lambda = decay constant
t = fractional hours (eg. 0.5 instead of 30)

How do we find an appropriate decay constant? First, we need to find a good half-life value. Let’s say 24 hours. Now we can solve for lambda:

1
2
3
t1/2 = ln 2 / lambda
24 = ln 2 / lambda
lambda = 0.02888

The general pseudo-logic is:

  1. Set a default temperature for new items (eg. 10.0)
  2. Pick the lambda (in this case, 0.02888)
  3. Pick a temperature increment (eg. 1.0)
  4. When there’s new activity on an item:
    • Calculate the item’s current temperature
    • Increment the item’s temperature
    • Update the item’s record along with the current time (Time.now)
  5. Sort items based on current temperature

In your model or controller, it would look like:

1
2
3
temp = @post.temperature * @post.cooling_rate(-0.02888, @post.last_reading)
temp += 1.0
@post.update_attributes(:temperature => temp, :last_temperature_at => Time.now)

And that’s it! You can now sort popular items based on exponential decay. Now you can easily tweak the half-life parameters or add additional signal weightings when incrementing the temperature.