Adamw has submitted this change and it was merged.

Change subject: split out test results functionality
......................................................................


split out test results functionality

Change-Id: Ibacee6c959f07eb65f5ddfc69566ae627c5dedf2
---
M live_analysis/config.py.example
M live_analysis/fr/centralnotice.py
M live_analysis/fr/impressions.py
M live_analysis/fr/tests/__init__.py
M live_analysis/fr/tests/campaign_log.py
A live_analysis/fr/tests/confidence.py
A live_analysis/fr/tests/results.py
M live_analysis/fr/tests/spec.py
8 files changed, 164 insertions(+), 162 deletions(-)

Approvals:
  Adamw: Verified; Looks good to me, approved



diff --git a/live_analysis/config.py.example b/live_analysis/config.py.example
index 0bae68f..d4eb178 100644
--- a/live_analysis/config.py.example
+++ b/live_analysis/config.py.example
@@ -37,3 +37,8 @@
 results_prefix = ''
 
 reference_day = '20121116000000'
+
+fr_banner_naming = 
r'B13_\d+_(?P<testname>[^_]+)_(?P<variation>[^_]+)_(?P<dropdown>[^_]+)_(?P<language>[a-z]{2})(?P<country>[A-Z]{2})'
+
+fudge_trials = 100000
+confidence_level = 0.95
diff --git a/live_analysis/fr/centralnotice.py 
b/live_analysis/fr/centralnotice.py
index 4133d29..3e40041 100644
--- a/live_analysis/fr/centralnotice.py
+++ b/live_analysis/fr/centralnotice.py
@@ -25,6 +25,7 @@
     } )
 
     if campaign in result:
+        result[campaign]['name'] = campaign
         cached_campaigns[campaign] = result[campaign]
         return cached_campaigns[campaign]
 
diff --git a/live_analysis/fr/impressions.py b/live_analysis/fr/impressions.py
index ccc7656..232df64 100644
--- a/live_analysis/fr/impressions.py
+++ b/live_analysis/fr/impressions.py
@@ -1,4 +1,4 @@
-def get_impressions(campaign=None, banner=None):
+def get_impressions(campaign=None, banner=None, **ignore):
     import db
 
     query = db.Query()
diff --git a/live_analysis/fr/tests/__init__.py 
b/live_analysis/fr/tests/__init__.py
index a4d22eb..b82f36d 100644
--- a/live_analysis/fr/tests/__init__.py
+++ b/live_analysis/fr/tests/__init__.py
@@ -4,54 +4,33 @@
 These are not unit tests ;) they are WMF Fundraising A/B tests.
 '''
 
-import re
-
 from fr.centralnotice import get_campaign
-from fr.contributions import get_totals
-from fr.impressions import get_impressions
-
-FR_LABEL_PATTERN = 
r'B13_\d+_(?P<testname>[^_]+)_(?P<variation>[^_]+)_(?P<dropdown>[^_]+)_(?P<language>[a-z]{2})(?P<country>[A-Z]{2})'
-FUDGE_TRIALS = 100000
-CONFIDENCE_LEVEL = 0.95
+from fr.tests.results import get_banner_results
 
 class FrTest(object):
-    def __init__(self, label=None, type="", campaigns=None, banners=None, 
start=None, end=None, disabled=False, **ignore):
-        #print "Warning: ignoring columns: %s" % (", ".join(ignore.keys()), )
+    def __init__(self, label=None, type="", campaign=None, banners=None, 
start=None, end=None, disabled=False, **ignore):
+        print "Warning: ignoring columns: %s" % (", ".join(ignore.keys()), )
 
-        self.campaigns = []
-        if campaigns:
-            if hasattr(campaigns, 'split'):
-                campaigns = [ s.strip() for s in campaigns.split(",") ]
-            for name in campaigns:
-                campaign = get_campaign(name)
-                if campaign:
-                    campaign['name'] = name
-                    self.campaigns.append(campaign)
-                else:
-                    print "Warning: no such campaign '%s'" % name
+        self.campaign = get_campaign(campaign)
+        if not self.campaign:
+            print "Warning: no such campaign '%s'" % campaign
 
-        type = type.lower()
+        self.type = type.lower()
 
-        if type.count('banner') > 0:
+        if self.type.count('banner') > 0:
             self.is_banner_test = True
             if banners:
                 if hasattr(banners, 'strip'):
                     banners = [ s.strip() for s in banners.split(",") ]
                 self.banners = banners
             else:
-                def reduce_banners(sum, campaign):
-                    if campaign['banners']:
-                        sum.extend(campaign['banners'].keys())
-                    return sum
+                if self.campaign['banners']:
+                    self.banners = self.campaign['banners'].keys()
 
-                self.banners = reduce(reduce_banners, self.campaigns, [])
+            #self.variations = [ FrTestVariation(banner=name) for name in 
self.banners ]
 
-            #self.variations = [ FrTestVariation(banner=b) for b in 
self.banners ]
-
-        self.is_country_test = (type.count('country') > 0)
-        self.is_lp_test = (type.count('lp') > 0)
-
-        self.type = type
+        self.is_country_test = (self.type.count('country') > 0)
+        self.is_lp_test = (self.type.count('lp') > 0)
 
         self.start_time = start
         self.end_time = end
@@ -63,61 +42,24 @@
         self.results = []
 
     def load_results(self):
-        for campaign in self.campaigns:
-            if self.is_banner_test and self.banners:
-                results = []
-                for name in self.banners:
-                    test_case = self.get_case(
-                        campaign=campaign['name'],
-                        banner=name
-                    )
-                    totals = get_totals(**test_case)
-                    impressions = get_impressions(campaign=campaign['name'], 
banner=name)
+        if self.is_banner_test and self.banners:
+            cases = []
+            for name in self.banners:
+                test_case = self.get_case(
+                    campaign=self.campaign['name'],
+                    banner=name
+                )
+                cases.append(test_case)
 
-                    result_extra = {
-                        'preview': 
"http://en.wikipedia.org/wiki/Special:Random?banner="; + name,
-                        'screenshot': 
"http://fundraising-archive.wmflabs.org/banner/%s.png"; % name,
+            self.results.extend(get_banner_results(cases))
 
-                        'impressions': str(impressions),
-                    }
-
-                    # FIXME: refactor to a variations hook
-                    match = re.match(FR_LABEL_PATTERN, name)
-                    if match:
-                        result_extra.update({
-                            'label': match.group("testname"),
-                            'language': match.group("language"),
-                            'variation': match.group("variation"),
-                            'dropdown': match.group("dropdown") is "dr",
-                            'country': match.group("country"),
-
-                            'preview': 
"http://en.wikipedia.org/wiki/Special:Random?banner=%s&country=%s&uselang=%s"; % 
(name, match.group("country"), match.group("language")),
-                        })
-
-                    results.append(TestResult(
-                        criteria=test_case,
-                        results=[totals, result_extra]
-                    ))
-
-                try:
-                    confidence = self.get_confidence(results, 'banner', 
'donations')
-                    if confidence:
-                        for i, levels in enumerate(confidence):
-                            results[i].add_result('p-value', 
levels.two_tailed_p_value)
-                            results[i].add_result('improvement', 
levels.relative_improvement.value * 100)
-                except ImportError as e:
-                    print "ERROR: not calculating confidence, dummy: ", 
e.message
-                results[0].add_result('confidencelink', 
self.get_confidence_link(results, 'banner', 'donations', FUDGE_TRIALS))
-
-                self.results.extend(results)
-
-            if self.is_country_test:
-                results = [ calculate_result(country=code) for code in 
campaign['countries'] ]
-                self.results.extend(results)
+        if self.is_country_test:
+            #results = [ calculate_result(country=code) for code in 
campaign['countries'] ]
+            #self.results.extend(results)
+            print "country test type not implemented"
 
         if self.is_lp_test:
             print "LP test type not implemented"
-            pass
 
     def get_case(self, **kw):
         conditions = {
@@ -131,7 +73,12 @@
     def __repr__(self):
         description = '''
 Test: %(label)s (%(campaigns)s) %(start)s - %(end)s
-''' % {'label': self.label, 'campaigns': str([c['name'] for c in 
self.campaigns]), 'start': self.start_time, 'end': self.end_time, }
+''' % {
+            'label': self.label,
+            'campaigns': self.campaign['name'],
+            'start': self.start_time,
+            'end': self.end_time,
+        }
         if not self.enabled:
             description += " DISABLED "
         if self.is_banner_test:
@@ -142,76 +89,4 @@
             description += " lps: " + str(self.lps)
         return description
 
-    def get_confidence(self, results, name_column=None, successes_column=None, 
trials=None):
-        from stats_abba import Experiment
-        num_test_cases = len(results)
-
-        if not num_test_cases:
-            return
-
-        results = sorted(results, key=lambda result: 
result.results[successes_column])
-        for result in results:
-            if result.results[successes_column]:
-                baseline_successes = result.results[successes_column]
-                break
-
-        if not baseline_successes:
-            return
-
-        experiment = Experiment(
-            num_trials=FUDGE_TRIALS,
-            baseline_num_successes=baseline_successes,
-            baseline_num_trials=FUDGE_TRIALS,
-            confidence_level=CONFIDENCE_LEVEL
-        )
-        #useMultipleTestCorrection=true
-
-        cases = []
-        for result in results:
-            name = result.results[name_column]
-            successes = result.results[successes_column]
-            if hasattr(trials, 'encode'):
-                trials = result.results[trials]
-            else:
-                trials = FUDGE_TRIALS
-            calculated = experiment.get_results(num_successes=successes, 
num_trials=trials)
-            cases.append(calculated)
-
-        return cases
-
-    def get_confidence_link(self, results, name_column, successes_column, 
trials):
-        cases = []
-        for result in results:
-            # skip empty results, usually these will be "blank" banners
-            if not result.results[successes_column]:
-                continue
-            name = result.results[name_column]
-            successes = result.results[successes_column]
-            if hasattr(trials, 'encode'):
-                trials = result.results[trials]
-            cases.append( "%s=%s,%s" % (name, successes, trials) )
-        return 
"http://www.thumbtack.com/labs/abba/#%s&abba:intervalConfidenceLevel=0.95&abba:useMultipleTestCorrection=true";
 % "&".join(cases)
-
-class TestResult(object):
-    def __init__(self, criteria=None, results=None):
-        self.criteria = criteria
-        self.results = {}
-        if results:
-            self.add_result(results)
-
-    def add_result(self, result, value=None):
-        if hasattr(result, 'keys'):
-            self.results.update(result)
-        elif hasattr(result, 'append'):
-            for entry in result:
-                self.add_result(entry)
-        else:
-            self.results[result] = value
-
-    def __repr__(self):
-        import json
-        return '''
-Result: %s
-  -> %s''' % (json.dumps(self.criteria, indent=4), json.dumps(self.results, 
indent=4), )
-
-#class TestVariation(object):
+#class FrTestVariation(object):
diff --git a/live_analysis/fr/tests/campaign_log.py 
b/live_analysis/fr/tests/campaign_log.py
index cc15330..2b0dc50 100644
--- a/live_analysis/fr/tests/campaign_log.py
+++ b/live_analysis/fr/tests/campaign_log.py
@@ -31,7 +31,7 @@
 
         return FrTest(
             type="banner",
-            campaigns=entry['campaign'],
+            campaign=entry['campaign'],
             banners=banners,
             start=start,
             end=end
diff --git a/live_analysis/fr/tests/confidence.py 
b/live_analysis/fr/tests/confidence.py
new file mode 100644
index 0000000..2c02f31
--- /dev/null
+++ b/live_analysis/fr/tests/confidence.py
@@ -0,0 +1,67 @@
+import config
+from stats_abba import Experiment
+
+def add_confidence(results, name_column, successes_column):
+    confidence = get_confidence(results, name_column, successes_column)
+    if confidence:
+        for i, levels in enumerate(confidence):
+            results[i].results.update({
+                'p-value': levels.two_tailed_p_value,
+                'improvement': levels.relative_improvement.value * 100,
+            })
+    results[0].results.update({
+        'confidencelink': get_confidence_link(results, name_column, 
successes_column)
+    })
+
+def get_confidence(results, name_column=None, successes_column=None, 
trials=None):
+    num_test_cases = len(results)
+
+    if not num_test_cases:
+        return
+
+    results = sorted(results, key=lambda result: 
result.results[successes_column])
+    baseline_successes = None
+    for result in results:
+        if result.results[successes_column]:
+            baseline_successes = result.results[successes_column]
+            break
+
+    if not baseline_successes:
+        return
+
+    experiment = Experiment(
+        num_trials=config.fudge_trials,
+        baseline_num_successes=baseline_successes,
+        baseline_num_trials=config.fudge_trials,
+        confidence_level=config.confidence_level
+    )
+    #useMultipleTestCorrection=true
+
+    cases = []
+    for result in results:
+        name = result.results[name_column]
+        successes = result.results[successes_column]
+        if hasattr(trials, 'encode'):
+            trials = result.results[trials]
+        else:
+            trials = config.fudge_trials
+        calculated = experiment.get_results(num_successes=successes, 
num_trials=trials)
+        cases.append(calculated)
+
+    return cases
+
+def get_confidence_link(results, name_column, successes_column):
+    cases = []
+    for result in results:
+        # skip empty results, usually these will be "blank" banners
+        if not result.results[successes_column]:
+            continue
+        name = result.results[name_column]
+        successes = result.results[successes_column]
+        cases.append( "%s=%s,%s" % (name, successes, config.fudge_trials) )
+
+    return 
"http://www.thumbtack.com/labs/abba/#%(cases)s&abba:intervalConfidenceLevel=%(confidence)s&abba:useMultipleTestCorrection=true"
 % {
+        'cases': "&".join(cases),
+        'confidence': config.confidence_level,
+    }
+
diff --git a/live_analysis/fr/tests/results.py 
b/live_analysis/fr/tests/results.py
new file mode 100644
index 0000000..8179732
--- /dev/null
+++ b/live_analysis/fr/tests/results.py
@@ -0,0 +1,54 @@
+import re
+import json
+
+import config
+from fr.contributions import get_totals
+from fr.impressions import get_impressions
+from fr.tests.confidence import add_confidence
+
+class TestResult(object):
+    def __init__(self, criteria=None, results={}):
+        self.criteria = criteria
+        self.results = results
+
+    def __repr__(self):
+        return '''
+Result: %s
+  -> %s''' % (
+            json.dumps(self.criteria, indent=4),
+            json.dumps(self.results, indent=4),
+        )
+
+
+def get_banner_results(cases):
+    results = [ banner_results(case) for case in cases ]
+
+    add_confidence(results, 'banner', 'donations')
+
+    return results
+
+def banner_results(criteria):
+    results = get_totals(**criteria)
+    impressions = get_impressions(**criteria)
+
+    results.update({
+        'preview': 
"http://en.wikipedia.org/wiki/Special:Random?banner=%s&reset=1"; % 
criteria['banner'],
+        'screenshot': "http://fundraising-archive.wmflabs.org/banner/%s.png"; % 
criteria['banner'],
+
+        'impressions': str(impressions),
+    })
+
+    # FIXME: refactor to a variations hook
+    match = re.match(config.fr_banner_naming, criteria['banner'])
+    if match:
+        results.update({
+            'label': match.group("testname"),
+            'language': match.group("language"),
+            'variation': match.group("variation"),
+            'dropdown': match.group("dropdown") is "dr",
+            'country': match.group("country"),
+
+            'preview': 
"http://en.wikipedia.org/wiki/Special:Random?banner=%s&country=%s&uselang=%s&reset=1";
 % (criteria['banner'], match.group("country"), match.group("language")),
+        })
+
+    return TestResult(criteria, results)
diff --git a/live_analysis/fr/tests/spec.py b/live_analysis/fr/tests/spec.py
index dd8e5b0..e121617 100644
--- a/live_analysis/fr/tests/spec.py
+++ b/live_analysis/fr/tests/spec.py
@@ -13,11 +13,11 @@
         yield FrTest(**row)
 
 def compare_test_fuzzy(a, b):
-    if a.campaigns == b.campaigns and a.banners == b.banners:
+    if a.campaign == b.campaign and a.banners == b.banners:
         return True
 
 def is_fr_test(test):
-    if test.label and test.banners and test.campaigns:
+    if test.label and test.banners and test.campaign:
         is_chapter = re.search(r'(_|\b)WM[A-Z]{2}(_|\b)', test.banners[0])
         return not is_chapter
 

-- 
To view, visit https://gerrit.wikimedia.org/r/48844
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ibacee6c959f07eb65f5ddfc69566ae627c5dedf2
Gerrit-PatchSet: 3
Gerrit-Project: wikimedia/fundraising/tools
Gerrit-Branch: master
Gerrit-Owner: Adamw <[email protected]>
Gerrit-Reviewer: Adamw <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to