Namespaces


In [ ]:
from planout.namespace import SimpleNamespace
from planout.experiment import SimpleExperiment, DefaultExperiment
from planout.ops.random import *
import pandas as pd

Namespaces are a way to manage multiple simultaneous or follow-on experiments. Namespace objects look like Experiment objects, but randomly assign primary units (e.g., users) to experiments. Units not assigned to experiments are given a default value, and are not logged.

Basic principles of PlanOut namespaces

  • Namespaces represent a set of related parameters. Experiments in the same namespaces are necessarily mutually exclusive.
  • Never modify an already running experiment
  • New experiments are registered and turned off through the namespace
  • Experiments are always analyzed separately.

Multiple experiments

Let's suppose we're iteratively running experiments, where we create a new experiment every few weeks as we learn things.


In [ ]:
class V1(SimpleExperiment):
  def assign(self, params, userid):
    params.banner_text = UniformChoice(
      choices=['Hello there!', 'Welcome!'],
      unit=userid)

class V2(SimpleExperiment):
  def assign(self, params, userid):
    params.banner_text = WeightedChoice(
      choices=['Hello there!', 'Welcome!'],
      weights=[0.8, 0.2],
      unit=userid)

class V3(SimpleExperiment):
  def assign(self, params, userid):
    params.banner_text = WeightedChoice(
      choices=['Nice to see you!', 'Welcome back!'],
      weights=[0.8, 0.2],
      unit=userid)

A default experiment

We also specify the experience of users not assigned to any experiments. Default experiments can return default key-value pairs.


In [ ]:
class DefaultBannerExperiment(DefaultExperiment):
  def get_default_params(self):
    return {'banner_text': 'Generic greetings!'}

Implementing a namespace with SimpleNamespace

Here is a namespace. It defines a name, the primary unit which gets hashed into conditions, the number of segments (should be 10,000 or 100,000 in production settings).


In [47]:
class BannerNamespace(SimpleNamespace):
  def setup(self):
    self.name = 'banner'
    self.primary_unit = 'userid'
    self.num_segments = 100
    self.default_experiment_class = DefaultBannerExperiment
    
  def setup_experiments(self):
    self.add_experiment('first version phase 1', V1, 30)
    self.add_experiment('first version phase 2', V1, 10)
    self.add_experiment('second version', V2, 40)

Try out a few users


In [48]:
BannerNamespace(userid=6).get('banner_text')


Out[48]:
'Hello there!'

In [50]:
for i in xrange(20):
    print BannerNamespace(userid=i).get('banner_text')


Welcome!
Hello there!
Generic greetings!
Hello there!
Generic greetings!
Hello there!
Hello there!
Welcome!
Hello there!
Welcome!
Generic greetings!
Hello there!
Generic greetings!
Generic greetings!
Hello there!
Generic greetings!
Hello there!
Generic greetings!
Hello there!
Hello there!

Now take a look at your local directory.

Underneeth the hood


In [ ]:
ns = BannerNamespace(userid=2)
ns.get_segment()

In [ ]:
ns.segment_allocations.get(ns.get_segment, 'no experiment allocated to segment')

In [ ]:
allocations = pd.DataFrame(ns.segment_allocations.items(), columns=['segment', 'experiment'])
print allocations[:8]
print allocations.groupby('experiment').agg(len)