Mercurial Hosting > luan
comparison src/luan/modules/lucene/Ab_testing.luan @ 775:1a68fc55a80c
simplify dir structure
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Fri, 26 Aug 2016 14:36:40 -0600 |
parents | lucene/src/luan/modules/lucene/Ab_testing.luan@ca169567ce07 |
children | bae2d0c2576c |
comparison
equal
deleted
inserted
replaced
774:3e30cf310e56 | 775:1a68fc55a80c |
---|---|
1 local Luan = require "luan:Luan.luan" | |
2 local pairs = Luan.pairs | |
3 local ipairs = Luan.ipairs | |
4 local error = Luan.error | |
5 local range = Luan.range | |
6 local Math = require "luan:Math.luan" | |
7 local Table = require "luan:Table.luan" | |
8 local String = require "luan:String.luan" | |
9 local gsub = String.gsub | |
10 local Io = require "luan:Io.luan" | |
11 local Http = require "luan:http/Http.luan" | |
12 local Logging = require "luan:logging/Logging.luan" | |
13 local Lucene = require "luan:lucene/Lucene.luan" | |
14 | |
15 local M = {} | |
16 | |
17 local logger = Logging.logger "Ab_testing" | |
18 | |
19 function M.of(index) | |
20 | |
21 local ab_testing = {} | |
22 | |
23 ab_testing.test_map = {} | |
24 | |
25 function ab_testing.test(test) | |
26 test.name or error "name not defined" | |
27 test.values or error "values not defined" | |
28 | |
29 -- list of event names | |
30 test.events or error "events not defined" | |
31 | |
32 -- map of event name to aggregator factory | |
33 test.aggregator_factories or error "aggregator_factories not defined" | |
34 | |
35 -- test.date_field is optional | |
36 | |
37 local field = "ab_test_" .. test.name | |
38 index.indexed_fields[field] == nil or error("test "+test.name+" already defined") | |
39 index.indexed_fields[field] = Lucene.type.string | |
40 test.field = field | |
41 | |
42 -- returns map of event name to (map of value to result) and "start_date" | |
43 function test.results() | |
44 local results = {} | |
45 for name in pairs(test.aggregator_factories) do | |
46 results[name] = {} | |
47 end | |
48 local date_field = test.date_field | |
49 local start_date = nil | |
50 for _, value in ipairs(test.values) do | |
51 local aggregators = {} | |
52 for name, factory in pairs(test.aggregator_factories) do | |
53 aggregators[name] = factory() | |
54 end | |
55 local query = field..":"..value | |
56 index.advanced_search(query, function(_,doc_fn) | |
57 local doc = doc_fn() | |
58 for _, aggregator in pairs(aggregators) do | |
59 aggregator.aggregate(doc) | |
60 end | |
61 if date_field ~= nil then | |
62 local date = doc[date_field] | |
63 if date ~= nil and (start_date==nil or start_date > date) then | |
64 start_date = date | |
65 end | |
66 end | |
67 end) | |
68 for name, aggregator in pairs(aggregators) do | |
69 results[name][value] = aggregator.result | |
70 end | |
71 end | |
72 results.start_date = start_date | |
73 return results | |
74 end | |
75 | |
76 function test.fancy_results() | |
77 local events = test.events | |
78 local results = test.results() | |
79 local fancy = {} | |
80 fancy.start_date = results.start_date | |
81 local event = events[1] | |
82 fancy[event] = {} | |
83 for value, count in pairs(results[event]) do | |
84 fancy[event][value] = {} | |
85 fancy[event][value].count = count | |
86 fancy[event][value].pct_of_total = 100 | |
87 fancy[event][value].pct_of_prev = 100 | |
88 end | |
89 local all = results[event] | |
90 local prev = all | |
91 for i in range(2,#events) do | |
92 event = events[i] | |
93 fancy[event] = {} | |
94 for value, count in pairs(results[event]) do | |
95 fancy[event][value] = {} | |
96 fancy[event][value].count = count | |
97 fancy[event][value].pct_of_total = M.percent(count,all[value]) | |
98 fancy[event][value].pct_of_prev = M.percent(count,prev[value]) | |
99 end | |
100 prev = results[event] | |
101 end | |
102 return fancy | |
103 end | |
104 | |
105 ab_testing.test_map[test.name] = test | |
106 | |
107 return test | |
108 end | |
109 | |
110 function ab_testing.value(test_name,values) | |
111 return values[test_name] or ab_testing.test_map[test_name].values[1] | |
112 end | |
113 | |
114 -- returns map from test name to value | |
115 function ab_testing.from_doc(doc) | |
116 local values = {} | |
117 for _, test in pairs(ab_testing.test_map) do | |
118 values[test.name] = doc[test.field] | |
119 end | |
120 return values | |
121 end | |
122 | |
123 function ab_testing.to_doc(doc,values,tests) | |
124 tests = tests or ab_testing.test_map | |
125 if values == nil then | |
126 values = {} | |
127 for _, test in pairs(tests) do | |
128 values[test.name] = test.values[Math.random(#test.values)] | |
129 end | |
130 end | |
131 for _, test in pairs(tests) do | |
132 doc[test.field] = values[test.name] | |
133 end | |
134 return values | |
135 end | |
136 | |
137 function ab_testing.web_page(test_names) | |
138 return function() | |
139 local results = {} | |
140 for _, name in ipairs(test_names) do | |
141 local test = ab_testing.test_map[name] | |
142 test or error("test not found: "..name) | |
143 results[name] = test.fancy_results() | |
144 end | |
145 Io.stdout = Http.response.text_writer() | |
146 M.html(test_names,ab_testing.test_map,results) | |
147 end | |
148 end | |
149 | |
150 return ab_testing | |
151 end | |
152 | |
153 | |
154 -- aggregator factories | |
155 | |
156 -- fn(doc) should return boolean whether doc should be counted | |
157 function M.count(fn) | |
158 return function() | |
159 local aggregator = {} | |
160 aggregator.result = 0 | |
161 function aggregator.aggregate(doc) | |
162 if fn(doc) then | |
163 aggregator.result = aggregator.result + 1 | |
164 end | |
165 end | |
166 return aggregator | |
167 end | |
168 end | |
169 | |
170 M.count_all = M.count( function(doc) return true end ) | |
171 | |
172 -- fn(doc) should return number to add to result, return 0 for nothing | |
173 function M.sum(fn) | |
174 return function() | |
175 local aggregator = {} | |
176 aggregator.result = 0 | |
177 function aggregator.aggregate(doc) | |
178 aggregator.result = aggregator.result + fn(doc) | |
179 end | |
180 return aggregator | |
181 end | |
182 end | |
183 | |
184 | |
185 | |
186 function M.percent(x,total) | |
187 if total==0 then | |
188 return 0 | |
189 else | |
190 return 100 * x / total | |
191 end | |
192 end | |
193 | |
194 -- I will change this to use SimplyHTML when this is used again. | |
195 local function basic_style() %> | |
196 body {font-family:'Arial',sans-serif;font-size:16px;padding:1em 2em} | |
197 h1 {font-weight:bold;font-size:20px} | |
198 h2 {margin:2em 0 0em;font-size:18px;color:#3589B1} | |
199 table.results {margin-top:.5em;border-collapse:collapse;font-size:90%} | |
200 table.results th {background:#eee} | |
201 table.results th,table.results td {border-left:1px solid #bbb;padding:.4em 2em} | |
202 table.results tr:nth-child(odd) td {background:#f8f8f8} | |
203 <% end | |
204 | |
205 local function format(v) | |
206 v = v .. '' | |
207 return gsub( v, [[(\d+\.\d{1})\d+]], '$1' ) | |
208 end | |
209 | |
210 function M.html(test_names,tests,results) %> | |
211 <!DOCTYPE html> | |
212 <html lang="en"> | |
213 <head> | |
214 <title>A/B Test Results</title> | |
215 <style><% basic_style() %></style> | |
216 </head> | |
217 <body> | |
218 <h1>A/B Test Results</h1> | |
219 <% | |
220 for _, test_name in ipairs(test_names) do | |
221 local test = tests[test_name] | |
222 local result = results[test_name] | |
223 local n = #test.values | |
224 %> | |
225 <h2><%=test_name%></h2> | |
226 <table class="results"> | |
227 <tr> | |
228 <th>Event</th> | |
229 <th class="top" colspan="<%=n%>">Count</th> | |
230 <th class="top" colspan="<%=n%>">% of total</th> | |
231 <th class="top" colspan="<%=n%>">% of prev</th> | |
232 </tr> | |
233 <tr> | |
234 <th></th> | |
235 <% | |
236 for _ in range(1,3) do | |
237 for _, value in ipairs(test.values) do | |
238 %><th class="top"><%=value%></th><% | |
239 end | |
240 end | |
241 %> | |
242 </tr> | |
243 <% | |
244 for _, event in ipairs(test.events) do | |
245 local event_values = result[event] | |
246 %> | |
247 <tr> | |
248 <td><%=event%></td> | |
249 <% | |
250 for _, value in ipairs(test.values) do | |
251 %><td><%=format(event_values[value].count)%></th><% | |
252 end | |
253 for _, value in ipairs(test.values) do | |
254 %><td><%=format(event_values[value].pct_of_total)%></th><% | |
255 end | |
256 for _, value in ipairs(test.values) do | |
257 %><td><%=format(event_values[value].pct_of_prev)%></th><% | |
258 end | |
259 %> | |
260 </tr> | |
261 <% | |
262 end | |
263 %> | |
264 </table> | |
265 <% | |
266 end | |
267 %> | |
268 </body> | |
269 </html> | |
270 <% end | |
271 | |
272 return M |