位运算实现用户留存率
統(tǒng)計(jì)留存率之前先弄清一下留存率的概念,百度百科中是這么說的:
用戶在某段時(shí)間內(nèi)開始使用應(yīng)用,經(jīng)過一段時(shí)間后,仍然繼續(xù)使用應(yīng)用的被認(rèn)作是留存;
這部分用戶占當(dāng)時(shí)新增用戶的比例即是留存率,會按照每隔1單位時(shí)間(例日、周、月)來進(jìn)行統(tǒng)計(jì)。
顧名思義,留存指的就是“有多少用戶留下來了”。留存用戶和留存率體現(xiàn)了應(yīng)用的質(zhì)量和保留用戶的能力。
簡單點(diǎn)說,第一天新增加了100個用戶,第二天這100個人有50個還有登錄,第三天這100個人還有30個有登錄。。。依次類推 那次日留存率為50%,三日留存為30% 。
在統(tǒng)計(jì)系統(tǒng)中經(jīng)常需要統(tǒng)計(jì)用戶留存率,這里整理下用戶留存率統(tǒng)計(jì)的幾種實(shí)現(xiàn)方式。
1、通過最后登錄時(shí)間實(shí)現(xiàn)
有一張唯一表來記錄新增用戶,這張表至少包含這三個字段: uid, reg_time, last_visited_time。用戶每次訪問后更新最后訪問時(shí)間(last_visited_time),假設(shè)3.6號新注冊100個用戶,需要統(tǒng)計(jì)次日留存,則在3.8號凌晨統(tǒng)計(jì)reg_time為3.6并且last_visited_time為3.7號即可,參考SQL:
SELECT COUNT(*) FROM TBL_NAME WHERE DATE(reg_time) = '2014-03-06' AND DATE(last_visited_time) = '2014-03-07'
實(shí)現(xiàn)起來很簡單,但問題也很明顯,如果恰好這些用戶0點(diǎn)有訪問,且先一步更新了訪問時(shí)間,留存率則記錄不到了,這個對整個的結(jié)果偏差不會太大,先忽略。有一個更明顯的問題就是無法重復(fù)統(tǒng)計(jì),如果腳本出錯或者需要重新統(tǒng)計(jì)則無法實(shí)現(xiàn)。當(dāng)然好處也有,就是統(tǒng)計(jì)方便,同時(shí)也方便新增N日留存。
2、通過建立獨(dú)立的字段實(shí)現(xiàn)
獨(dú)立的字段可以這么設(shè)計(jì),uid,reg_time,day_2,day_3,day_4…等等,當(dāng)用戶第二天有訪問時(shí)更新day_2的字段為1,第三日訪問更新day_3為1,該系列字段默認(rèn)為0。同樣的統(tǒng)計(jì)次日留存,則SQL應(yīng)該是這樣子:
SELECT COUNT(*) FROM TBL_NAME WHERE DATE(reg_time) = '2014-03-06' AND day_2 = 1
該方法可以重復(fù)統(tǒng)計(jì)了,但又不方便擴(kuò)展了,如果當(dāng)前沒有考慮到15天流程,則需要修改表結(jié)構(gòu),新增day_15才行。
3、通過位運(yùn)算實(shí)現(xiàn)
上面的數(shù)據(jù)表中記錄的值就是很多的0和1,可以用這些二進(jìn)制的0和1來表示當(dāng)天是否有訪問過,1表示有訪問過,0表示未訪問過。設(shè)計(jì)表中有這幾個字段,uid,reg_time,retension,假設(shè)留存用retention記錄,則
第一天訪問 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 對應(yīng)十進(jìn)制的1,retention記錄為1
第二天訪問 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 第二天有訪問后retention更新為3
第四天訪問 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 第三天沒有訪問,第四天訪問后rentention更新為11
依次類推,接下來就是計(jì)算該天的留存,以次日留存為例。將次日的數(shù)據(jù)與第2位為1其他位為0的值做按位與操作
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 & 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
按位與是將都為1的設(shè)置為1,如果用整數(shù)來表示,求次日留存是 3 & 2 ,如果結(jié)果為2則表示次日有訪問過,如果不為2結(jié)果為0則說明沒有訪問過。所以求第N天的sql應(yīng)該是(N表示第N天留存,如第3天用第3位來表示就是2的2次方):
SELECT COUNT(*) FROM TBL_NAME WHERE DATE(reg_time) = 'XXXX-XX-XX' AND retention & 2^(N-1)
當(dāng)然這里的第幾天實(shí)際表示第幾日留存可以自己定,如果第10位表示30日留存,則將retention與2^9求按位與即可求得30日留存。
這里解決了讀的問題,還有寫的問題,首次注冊時(shí)值為0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 ,第二天有訪問則將前一天的值與第二位為1其他位為0的做按位或操作即可,按位或是將其中任何一個為 1 的位設(shè)為 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1
第三天沒有訪問,第四天訪問則是
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 | 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 = 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1
用SQL來表示就是(N表示第N天訪問)
UPDATE TBL_NAME SET retention = retention | 2^(N-1) WHERE uid = 'XX'
而且該更新操作在當(dāng)天是可以重復(fù)操作的,因?yàn)榘次换蛑恍枰幸粋€為1即可,第2天第一次更新1 | 2 = 3,第二次更新3 | 2 = 3??梢娭凳窍嗤?。 聽到這種方案后也懷疑效率問題,在1000w數(shù)據(jù)中統(tǒng)計(jì)速度與reg_time中索引時(shí)間差不多,所以問題不大;一個整形4個字節(jié)32位,可以表示32個不同的留存,整形不夠也可以用長整型8個字節(jié)的??傮w看來該方法可擴(kuò)展,可重新統(tǒng)計(jì),所以可行。
位運(yùn)算之前只在權(quán)限中見過,這里用法也是一種不錯的方式,期待更多的思考,下面是位運(yùn)算的基本操作:
總結(jié)
以上是生活随笔為你收集整理的位运算实现用户留存率的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 合军聚众和整装待发哪个好
- 下一篇: 不再迷惑,无值和 NULL 值