Campaign analytics - unique users, dropped count

Fixing incorrect dropped counts.
Restricting start/finish level events to unique users.
Does not fix campaign editor level view analytics.
This commit is contained in:
Matt Lott 2015-01-06 21:38:45 -08:00
parent 4ea2a51ff6
commit d89b0d90e3
3 changed files with 119 additions and 22 deletions

View file

@ -0,0 +1,82 @@
# Calculate level completion rates via mixpanel export API
# TODO: unique users
# TODO: align output
# TODO: order output
import sys
from mixpanel import Mixpanel
try:
import json
except ImportError:
import simplejson as json
# NOTE: mixpanel dates are by day and inclusive
# E.g. '2014-12-08' is any date that day, up to 2014-12-09 12am
if __name__ == '__main__':
if not len(sys.argv) is 3:
print "Script format: <script> <api_key> <api_secret>"
else:
api_key = sys.argv[1]
api_secret = sys.argv[2]
api = Mixpanel(
api_key = api_key,
api_secret = api_secret
)
startDate = '2014-12-31'
endDate = '2015-01-05'
print("Requesting data for {0} to {1}".format(startDate, endDate))
data = api.request(['export'], {
'event' : ['Started Level', 'Saw Victory'],
'from_date' : startDate,
'to_date' : endDate
})
levelRates = {}
lines = data.split('\n')
print "Received %d entries" % len(lines)
for line in lines:
try:
if len(line) is 0: continue
eventData = json.loads(line)
eventName = eventData['event']
if not eventName in ['Started Level', 'Saw Victory']:
print 'Unexpected event ' + eventName
break
properties = eventData['properties']
if 'levelID' in properties:
levelID = properties['levelID']
elif 'level' in properties:
levelID = properties['level'].lower().replace(' ', '-')
else:
print("Unkonwn levelID for", eventName)
print(properties)
break
if not levelID in levelRates:
levelRates[levelID] = {'started': 0, 'finished': 0}
if eventName == 'Started Level':
levelRates[levelID]['started'] += 1
elif eventName == 'Saw Victory':
levelRates[levelID]['finished'] += 1
else:
print("Unknown event name", eventName)
print(eventData)
break
except:
print "Unexpected error:", sys.exc_info()[0]
print line
break
# print(levelRates)
for levelID in levelRates:
started = levelRates[levelID]['started']
finished = levelRates[levelID]['finished']
# if not levelID == 'endangered-burl':
# continue
if started > 0:
print("{0}\t{1}\t{2}\t{3}%".format(levelID, started, finished, float(finished) / started * 100))
else:
print("{0}\t{1}\t{2}".format(levelID, started, finished))

View file

@ -20,12 +20,15 @@ print("Today is " + today);
var todayMinus6 = new Date();
todayMinus6.setDate(todayMinus6.getUTCDate() - 6);
var startDate = todayMinus6.toISOString().substr(0, 10) + "T00:00:00.000Z";
// startDate = "2014-12-01T00:00:00.000Z";
// startDate = "2014-12-31T00:00:00.000Z";
print("Start date is " + startDate)
// var endDate = "2015-01-06T00:00:00.000Z";
// print("End date is " + endDate)
var cursor = db['analytics.log.events'].find({
$and: [
{"created": { $gte: ISODate(startDate)}},
// {"created": { $lt: ISODate(endDate)}},
{$or: [ {"event" : 'Started Level'}, {"event" : 'Saw Victory'}]}
]
});
@ -115,6 +118,7 @@ var campaigns = {
// Bucketize events by user
print("Getting event data...");
var userProgression = {};
var userLevelEventMap = {}; // Only want unique users per-level/event
while (cursor.hasNext()) {
var doc = cursor.next();
var created = doc.created;
@ -124,12 +128,17 @@ while (cursor.hasNext()) {
if (level) {
if (level.length > longestLevelName) longestLevelName = level.length;
var user = doc.user.valueOf();
if (!userProgression[user]) userProgression[user] = [];
userProgression[user].push({
created: created,
event: event,
level: level
});
if (!userLevelEventMap[user]) userLevelEventMap[user] = {};
if (!userLevelEventMap[user][level]) userLevelEventMap[user][level] = {};
if (!userLevelEventMap[user][level][event]) {
userLevelEventMap[user][level][event] = true;
if (!userProgression[user]) userProgression[user] = [];
userProgression[user].push({
created: created,
event: event,
level: level
});
}
}
}
longestLevelName += 2;
@ -224,7 +233,7 @@ for (campaign in campaigns) {
print("\n" + campaign);
var level = "level";
var levelSpacer = new Array(longestLevelName - level.length).join(' ');
print(level + levelSpacer + "started\tdropped\t\tfinished dropped");
print(level + levelSpacer + "started\tdropped\t\tfinished dropped\tcompletion");
for (var i = 0; i < campaignRates[campaign].levels.length; i++) {
var level = campaignRates[campaign].levels[i].level;
var started = campaignRates[campaign].levels[i].started;
@ -232,13 +241,13 @@ for (campaign in campaigns) {
var finished = campaignRates[campaign].levels[i].finished;
var finishDropped = campaignRates[campaign].levels[i].finishDropped;
var levelSpacer = new Array(longestLevelName - level.length).join(' ');
print(level + levelSpacer + started + "\t" + (started < 100 ? "\t" : "") + startDropped + "\t" + (startDropped / started * 100).toFixed(2) + "%\t" + finished + "\t" + finishDropped + "\t" + (finishDropped / finished * 100).toFixed(2) + "%");
print(level + levelSpacer + started + "\t" + (started < 100 ? "\t" : "") + startDropped + "\t" + (startDropped / started * 100).toFixed(2) + "%\t" + finished + "\t" + finishDropped + "\t" + (finishDropped / finished * 100).toFixed(2) + "%" + "\t" + (finished / started * 100).toFixed(2) + "%");
}
var level = 'Overall';
var started = campaignRates[campaign].overall.started;
var startDropped = campaignRates[campaign].overall.startDropped;
var finished = campaignRates[campaign].overall.finished;
var finishDropped = campaignRates[campaign].overall.finishDropped;
var levelSpacer = new Array(longestLevelName - level.length).join(' ');
print(level + levelSpacer + started + "\t" + (started < 100 ? "\t" : "") + startDropped + "\t" + (startDropped / started * 100).toFixed(2) + "%\t" + finished + "\t" + finishDropped + "\t" + (finishDropped / finished * 100).toFixed(2) + "%");
// var level = 'Overall';
// var started = campaignRates[campaign].overall.started;
// var startDropped = campaignRates[campaign].overall.startDropped;
// var finished = campaignRates[campaign].overall.finished;
// var finishDropped = campaignRates[campaign].overall.finishDropped;
// var levelSpacer = new Array(longestLevelName - level.length).join(' ');
// print(level + levelSpacer + started + "\t" + (started < 100 ? "\t" : "") + startDropped + "\t" + (startDropped / started * 100).toFixed(2) + "%\t" + finished + "\t" + finishDropped + "\t" + (finishDropped / finished * 100).toFixed(2) + "%");
}

View file

@ -33,6 +33,7 @@ class AnalyticsLogEventHandler extends Handler
# endDay - Exclusive, optional, e.g. '2014-12-16'
# TODO: An uncached call takes about 15s locally
# TODO: Use unique users
levelSlug = req.query.slug or req.body.slug
startDay = req.query.startDay or req.body.startDay
@ -142,6 +143,7 @@ class AnalyticsLogEventHandler extends Handler
# Bucketize events by user
userProgression = {}
userLevelEventMap = {} # Only want unique users per-level/event
for item in data
created = item.get('created')
event = item.get('event')
@ -151,14 +153,18 @@ class AnalyticsLogEventHandler extends Handler
level = item.get('properties.levelID')
continue unless level?
user = item.get('user')
userProgression[user] ?= []
userProgression[user].push
created: created
event: event
level: level
userLevelEventMap[user] ?= {}
userLevelEventMap[user][level] ?= {}
unless userLevelEventMap[user][level][event]
userLevelEventMap[user][level][event] = true
userProgression[user] ?= []
userProgression[user].push
created: created
event: event
level: level
# Order user progression by created
for user in userProgression
for user of userProgression
userProgression[user].sort (a,b) -> if a.created < b.created then return -1 else 1
# Per-level start/drop/finish/drop