Skip to content

Commit

Permalink
Rename "Variant" to "Version"
Browse files Browse the repository at this point in the history
  • Loading branch information
kaedroho committed Dec 16, 2020
1 parent c61fcd4 commit 856d51b
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 106 deletions.
31 changes: 31 additions & 0 deletions wagtail_ab_testing/migrations/0009_rename_variant_to_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 3.1.3 on 2020-12-16 13:26

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('wagtail_ab_testing', '0008_finished_status'),
]

operations = [
migrations.RenameField(
model_name='abtest',
old_name='winning_variant',
new_name='winning_version',
),
migrations.AlterModelOptions(
name='abtesthourlylog',
options={'ordering': ['ab_test', 'version', 'date', 'hour']},
),
migrations.RenameField(
model_name='abtesthourlylog',
old_name='variant',
new_name='version',
),
migrations.AlterUniqueTogether(
name='abtesthourlylog',
unique_together={('ab_test', 'version', 'date', 'hour')},
),
]
76 changes: 38 additions & 38 deletions wagtail_ab_testing/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Status(models.TextChoices):
FINISHED = 'finished', __('Finished')
COMPLETED = 'completed', __('Completed')

class Variant(models.TextChoices):
class Version(models.TextChoices):
CONTROL = 'control', __('Control')
TREATMENT = 'treatment', __('Treatment')

Expand All @@ -64,7 +64,7 @@ class CompletionAction(models.TextChoices):
sample_size = models.PositiveIntegerField(validators=[MinValueValidator(1)])
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='+')
status = models.CharField(max_length=20, choices=Status.choices, default=Status.DRAFT)
winning_variant = models.CharField(max_length=9, null=True, choices=Variant.choices)
winning_version = models.CharField(max_length=9, null=True, choices=Version.choices)
first_started_at = models.DateTimeField(null=True)

# Because an admin can pause/resume tests, we need to make sure we record the amount of time it has been running
Expand Down Expand Up @@ -154,9 +154,9 @@ def finish(self):
method.
"""
self.status = self.Status.FINISHED
self.winning_variant = self.check_for_winner()
self.winning_version = self.check_for_winner()

self.save(update_fields=['status', 'winning_variant'])
self.save(update_fields=['status', 'winning_version'])

@transaction.atomic
def complete(self, action, user=None):
Expand Down Expand Up @@ -188,34 +188,34 @@ def complete(self, action, user=None):
elif action == AbTest.CompletionAction.PUBLISH:
self.treatment_revision.publish(user=user)

def add_participant(self, variant=None):
def add_participant(self, version=None):
"""
Inserts a new participant into the log. Returns the variant that they should be shown.
Inserts a new participant into the log. Returns the version that they should be shown.
"""
# Get current numbers of participants for each variant
# Get current numbers of participants for each version
stats = self.hourly_logs.aggregate(
control_participants=Sum('participants', filter=Q(variant=self.Variant.CONTROL)),
treatment_participants=Sum('participants', filter=Q(variant=self.Variant.TREATMENT)),
control_participants=Sum('participants', filter=Q(version=self.Version.CONTROL)),
treatment_participants=Sum('participants', filter=Q(version=self.Version.TREATMENT)),
)
control_participants = stats['control_participants'] or 0
treatment_participants = stats['treatment_participants'] or 0

# Create an equal number of participants for each variant
if variant is None:
# Create an equal number of participants for each version
if version is None:
if treatment_participants > control_participants:
variant = self.Variant.CONTROL
version = self.Version.CONTROL

elif treatment_participants < control_participants:
variant = self.Variant.TREATMENT
version = self.Version.TREATMENT

else:
variant = random.choice([
self.Variant.CONTROL,
self.Variant.TREATMENT,
version = random.choice([
self.Version.CONTROL,
self.Version.TREATMENT,
])

# Add new participant to statistics model
AbTestHourlyLog._increment_stats(self, variant, 1, 0)
AbTestHourlyLog._increment_stats(self, version, 1, 0)

# If we have now reached the required sample size, end the test
# Note: we don't care too much that the last few participants won't
Expand All @@ -225,33 +225,33 @@ def add_participant(self, variant=None):
if control_participants + treatment_participants + 1 >= self.sample_size:
self.finish()

return variant
return version

def log_conversion(self, variant, *, time=None):
def log_conversion(self, version, *, time=None):
"""
Logs when a participant completed the goal.
Note: It's up to the caller to make sure that this doesn't get called more than once
per participant.
"""
AbTestHourlyLog._increment_stats(self, variant, 0, 1, time=time)
AbTestHourlyLog._increment_stats(self, version, 0, 1, time=time)

def check_for_winner(self):
"""
Performs a Chi-Squared test to check if there is a clear winner.
Returns Variant.CONTROL or Variant.TREATMENT if there is one. Otherwise, it returns None.
Returns Version.CONTROL or Version.TREATMENT if there is one. Otherwise, it returns None.
For more information on what the Chi-Squared test does, see:
https://www.evanmiller.org/ab-testing/chi-squared.html
https://towardsdatascience.com/a-b-testing-with-chi-squared-test-to-maximize-conversions-and-ctrs-6599271a2c31
"""
# Fetch stats from database
stats = self.hourly_logs.aggregate(
control_participants=Sum('participants', filter=Q(variant=self.Variant.CONTROL)),
control_conversions=Sum('conversions', filter=Q(variant=self.Variant.CONTROL)),
treatment_participants=Sum('participants', filter=Q(variant=self.Variant.TREATMENT)),
treatment_conversions=Sum('conversions', filter=Q(variant=self.Variant.TREATMENT)),
control_participants=Sum('participants', filter=Q(version=self.Version.CONTROL)),
control_conversions=Sum('conversions', filter=Q(version=self.Version.CONTROL)),
treatment_participants=Sum('participants', filter=Q(version=self.Version.TREATMENT)),
treatment_conversions=Sum('conversions', filter=Q(version=self.Version.TREATMENT)),
)
control_participants = stats['control_participants'] or 0
control_conversions = stats['control_conversions'] or 0
Expand Down Expand Up @@ -284,9 +284,9 @@ def check_for_winner(self):
# There is a clear winner!
# Return the one with the highest success rate
if (control_conversions / control_participants) > (treatment_conversions / treatment_participants):
return self.Variant.CONTROL
return self.Version.CONTROL
else:
return self.Variant.TREATMENT
return self.Version.TREATMENT

def get_status_description(self):
"""
Expand All @@ -300,10 +300,10 @@ def get_status_description(self):
return status + f" ({completeness_percentange}%)"

elif self.status in [AbTest.Status.FINISHED, AbTest.Status.COMPLETED]:
if self.winning_variant == AbTest.Variant.CONTROL:
if self.winning_version == AbTest.Version.CONTROL:
return status + " (" + _("Control won") + ")"

elif self.winning_variant == AbTest.Variant.TREATMENT:
elif self.winning_version == AbTest.Version.TREATMENT:
return status + " (" + _("Treatment won") + ")"

else:
Expand All @@ -315,7 +315,7 @@ def get_status_description(self):

class AbTestHourlyLog(models.Model):
ab_test = models.ForeignKey(AbTest, on_delete=models.CASCADE, related_name='hourly_logs')
variant = models.CharField(max_length=9, choices=AbTest.Variant.choices)
version = models.CharField(max_length=9, choices=AbTest.Version.choices)
date = models.DateField()
# UTC hour. Values range from 0 to 23
hour = models.PositiveSmallIntegerField()
Expand All @@ -327,9 +327,9 @@ class AbTestHourlyLog(models.Model):
conversions = models.PositiveIntegerField(default=0)

@classmethod
def _increment_stats(cls, ab_test, variant, participants, conversions, *, time=None):
def _increment_stats(cls, ab_test, version, participants, conversions, *, time=None):
"""
Increments the participants/conversions statistics for the given ab_test/variant.
Increments the participants/conversions statistics for the given ab_test/version.
This will create a new AbTestHourlyLog record if one doesn't exist for the current hour.
"""
Expand All @@ -342,15 +342,15 @@ def _increment_stats(cls, ab_test, variant, participants, conversions, *, time=N
with connection.cursor() as cursor:
table_name = connection.ops.quote_name(cls._meta.db_table)
query = """
INSERT INTO %s (ab_test_id, variant, date, hour, participants, conversions)
INSERT INTO %s (ab_test_id, version, date, hour, participants, conversions)
VALUES (%%s, %%s, %%s, %%s, %%s, %%s)
ON CONFLICT (ab_test_id, variant, date, hour)
ON CONFLICT (ab_test_id, version, date, hour)
DO UPDATE SET participants = %s.participants + %%s, conversions = %s.conversions + %%s;
""" % (table_name, table_name, table_name)

cursor.execute(query, [
ab_test.id,
variant,
version,
date,
hour,
participants,
Expand All @@ -362,7 +362,7 @@ def _increment_stats(cls, ab_test, variant, participants, conversions, *, time=N
# Fall back to running two queries (with small potential for race conditions if things run slowly)
hourly_log, created = cls.objects.get_or_create(
ab_test=ab_test,
variant=variant,
version=version,
date=date,
hour=hour,
defaults={
Expand All @@ -377,9 +377,9 @@ def _increment_stats(cls, ab_test, variant, participants, conversions, *, time=N
hourly_log.save(update_fields=['participants', 'conversions'])

class Meta:
ordering = ['ab_test', 'variant', 'date', 'hour']
ordering = ['ab_test', 'version', 'date', 'hour']
unique_together = [
('ab_test', 'variant', 'date', 'hour'),
('ab_test', 'version', 'date', 'hour'),
]


Expand Down
22 changes: 11 additions & 11 deletions wagtail_ab_testing/static_src/style/progress.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ $charcoal-grey: #333;
margin-bottom: 20px;
overflow: auto;

&__variant {
&__version {
float: left;
width: 50%;
box-sizing: border-box;
Expand Down Expand Up @@ -83,7 +83,7 @@ $charcoal-grey: #333;
}
}

&__variant-heading {
&__version-heading {
height: 30px;
border-bottom-width: 5px;
border-bottom-style: solid;
Expand All @@ -102,21 +102,21 @@ $charcoal-grey: #333;
vertical-align: -11%;
}
}
&__variant--control &__variant-heading {
&__version--control &__version-heading {
border-bottom-color: $color-control;
}
&__variant--treatment &__variant-heading {
&__version--treatment &__version-heading {
border-bottom-color: $color-treatment;
}

&__variant--control &__variant-heading--winner {
&__version--control &__version-heading--winner {
background-color: $color-control;
}
&__variant--treatment &__variant-heading--winner {
&__version--treatment &__version-heading--winner {
background-color: $color-treatment;
}

&__variant-inner {
&__version-inner {
border: 1px solid #eeeeee;
border-top: none;
box-sizing: border-box;
Expand All @@ -134,7 +134,7 @@ $charcoal-grey: #333;
}
}

&__variant-stats {
&__version-stats {
list-style-type: none;
margin: 0;
padding: 0;
Expand All @@ -147,18 +147,18 @@ $charcoal-grey: #333;
}
}

&__variant-stats > li {
&__version-stats > li {
float: left;
padding-right: 30px;
margin-top: 30px;
}

&__variant-stat {
&__version-stat {
font-weight: bold;
font-size: 50px;
}

&__variant-stat-name {
&__version-stat-name {
text-transform: uppercase;
font-size: 20px;
color: $charcoal-grey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ <h3>{% trans "Sample size" %}</h3>
<div class="nice-padding">
<div class="help-block help-info">
<p>{% trans "A/B tests are calculated using Pearson's chi squared test and are set at 95% confidence level." %}</p>
<p>{% trans "Traffic is split evenly between each variant." %}</p>
<p>{% trans "Traffic is split evenly between each version." %}</p>
<p>{% trans 'Users with "<a href="https://en.wikipedia.org/wiki/Do_Not_Track" target="_blank">Do Not Track</a>" enabled are not counted.' %}</p>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions wagtail_ab_testing/templates/wagtail_ab_testing/compare.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{% extends "wagtailadmin/base.html" %}
{% load wagtailadmin_tags i18n %}
{% block titletag %}{% trans "Compare A/B test variants" %} - {{ page.title }}{% endblock %}
{% block titletag %}{% trans "Compare A/B test versions" %} - {{ page.title }}{% endblock %}

{% block content %}
{% trans "Compare A/B test variants" as title %}
{% trans "Compare A/B test versions" as title %}
{% include "wagtailadmin/shared/header.html" with title=title subtitle=page.title tabbed=1 merged=1 %}

<div class="nice-padding" style="padding-top: 20px;">
Expand Down
Loading

0 comments on commit 856d51b

Please sign in to comment.