保姆級教學:手把手教你如何實現同期羣分析

↑ 點擊上方 凹凸數據” 關注 + 星標 ~ 

每天更新,大概率是晚9點  


作者簡介
HeoiJin:立志透過數據看清世界的產品策劃,專注爬蟲、數據分析、產品策劃領域。萬物皆營銷 | 資本永不眠 | 數據恆真理
CSDN:https://me.csdn.net/weixin_40679090

目錄: 

 

  1. 前言

  2. 項目準備

  3. 同期羣分析概念講解

  4. 材料梳理

  5. Excel實現

  6. MySQL實現

  7. Python實現

  8. 覆盤總結

一、前言

後互聯網時代,獲客拉新的成本越來越高,如何增加客戶的留存,提高客戶的復購次數、購買金額等變得十分重要,同期羣分析便是當中非常重要的分析方法。

關於同期羣分析概念和思路的文章很多,但分享如何實現的文章非常罕見。因此,本文將簡單介紹同期羣分析的概念,並用數據分析師的三板斧ESP(Excel、MySQL、Python)分別實現同期羣分析。

二、項目準備

  • Excel:

    • office或wps均可,office 2013後的版本更好

  • MySQL:

    • 版本:8.0(本次不涉及窗口函數,其他版本亦可)

    • Navicat

  • Python:

    • 版本:3.7

    • IDE:pycharm

    • 庫:pandas、xlrt

PS.

  • 因篇幅原因,可能會有未能詳細講解的過程

  • 完整源碼及數據集請移步至文末鏈接或閱讀原文

三、同期羣分析概念講解

數據分析最終目標都是爲了解決業務問題,任何分析方法都只是工具。因此在詳細講解如何實現之前,需要先明晰方法的含義是什麼,能帶來什麼收益,才能在合適的問題上選對分析方法。

3.1 同期羣分析含義

同期羣(Cohort)即相同時間內具有相似或特定屬性 、行爲的羣體。核心要素爲時間+特定屬性,比如把00後出生的人劃分爲一個羣組。

同期羣分析指將用戶進行同期羣劃分後,對比不同同期羣用戶的相同指標。我們耳熟能詳的留存率就是同期羣分析的其中一種,案例如下圖:

同期羣分析包含了3個重要元素:

  1. 客戶首次行爲時間,這是我們劃分同期羣的依據

  2. 時間維度,即上圖中+N月或者N日留存率中的N日

  3. 指標,註冊轉化率、付款轉化率、留存率等等

3.2 意義

同期羣分析給到更加細緻的衡量指標,幫助我們實時監控真實的用戶行爲、衡量用戶價值,併爲營銷方案的優化和改進提供支撐:
  • 橫向比較:觀察同一同期羣在不同生命週期下的行爲變化,推測相似羣體的行爲隨時間的變化

  • 縱向比較:觀察不同的同期羣在同一個生命週期下的行爲變化,驗證業務行爲是否取得預期效果

四、材料梳理

4.1 數據情況梳理

拿到數據的第一步,自然是瞭解數據的情況。針對本次同期羣分析,我們可能需要用到的字段有:

  • 客戶暱稱

  • 付款時間:時間戳形式

  • 訂單狀態:交易失敗/交易成功

  • 支付金額

  • 購買數量

通過進一步計算,發現付款時間中缺失值所在行的訂單狀態均爲“交易失敗”,那麼下文分析都需要將訂單狀態爲“交易失敗”的行全部剔除。

4.2 分析方法確定

針對此份數據,有3個分析方向可以選擇:

  1. 留存率或付款率

  2. 人均付款金額

  3. 人均購買次數

我們選擇其中最經典,也是數分面試中最常考的留存率作爲例子,需要用到的字段有:

  • 客戶暱稱

  • 付款時間

  • 訂單狀態

相信各位對留存率都十分熟悉,不過多介紹。在本次的分析中,留存率的具體計算方式爲:+N月留存率=(+N月付款用戶數/首月付款用戶數)*100%

注意:公式中的+N月存在歧義,會有兩種計算方法:

  1. 以自然月作爲月份偏移的依據:即所有首次行爲在9月的用戶,只要10月有付款行爲,都計算進+1月留存

  2. 以每30天作爲月份偏移的依據:即9月30日首次付款的用戶,在10月30日-11月29日之間有付款行爲,才計算進+1月留存

具體的差距會在Excel(用算法1)和MySQL(用算法2)兩種工具實現的結果中分別展示。沒有相關技術背景的看官老爺可直接對比最終的留存率結果。

五、Excel實現

Excel的實現方式是三個當中門檻最低的,只需要掌握數據透視表和一些基礎函數,但過程相對繁雜。實現思路如下:

實現思路一共分爲4大部分:數據清洗 -> 計算首單時間 -> 計算首單時間與付款時間差 -> 利用透視表計算同期羣留存量和留存率。其中由於部分版本的office和wps的數據透視表不支持非重複計數,因此需要先計算各月中各用戶出現的次數。

數據清洗部分只需要篩選+刪除便可完成,相信如此簡單的操作難不倒各位看官老爺們,那麼我們便從第二部分開始詳細講解。

5.1 計算每個客戶首單時間

首先通過數據透視表求每一個用戶首次付款時間。數據透視表,說白了就是通過特定的條件進行分組,並對數據進行求和、求均值、求方差等聚合操作。在製作數據透視表時要注意以下幾點:

  1. 數據區域的第一行爲標題欄(字段名稱)

  2. 標題欄不能出現空單元格,亦不要出現重複的標題名

  3. 數據中避免有合併單元格

  4. 不能出現非法日期

5.1.1 創建透視表

全選數據 -> 插入 -> 數據透視表 -> 確定

5.1.2 選擇分組字段和值字段

將“客戶暱稱”拖進“行”,將付款時間拖進“值”,並將值字段設置中的彙總方式設置爲最小值

這裏最小付款時間顯示爲10位的時間戳,只要調整顯示格式便可轉爲我們常見的xx年xx月xx日。

5.1.3 將首單時間拼接到每個用戶所在行

此步驟需要使用到vlookup函數進行匹配。VLOOKUP函數是一個縱向查找的函數,包含4個參數,具體語法爲=VLOOKUP(查找的依據,查找的區域,返回的值在查找區域中的列號,是否近似匹配)

注意:

  1. 查找的位置如果要保持不變,要使用A:B或者$A$1:$B$15的形式鎖定匹配區域

  2. 參數[ 查找的位置 ]中,“!”號前爲表的名稱

  3. 列號的計數是從1開始,且第一列必須是與查找依據對應的列

  4. 近似匹配參數中,0爲否(即必須與查找依據一模一樣才匹配),1爲是(即依據爲“同期”時,可以匹配出“同期”、“同期羣”或者“同期羣分析”)

=VLOOKUP(A2,首付時間透視表!A:B,2,0)

利用VLOOKUP拼接之後,首單時間同樣顯示爲10位的時間戳,設置單元格格式後即可顯示爲上圖的形式。

5.2 計算時間差

5.2.1 對付款時間和首單時間進行降採樣

如按算法2進行計算,可直接省略此步驟。

可能有看官老爺對重採樣的概念並不是很清楚,簡單說下:

  • 將時間序列從一個頻率轉化爲另外一個頻率的過程即重採樣

  • 常見的時間頻率由低到高依次爲:年 -> 月 -> 日 -> 時 -> 分 -> 秒

  • 將高頻率轉爲低頻率爲降採樣,將低頻率轉爲高頻率爲升採樣

在Excel當中可以使用分列或者時間相關函數(YEAR、MONTH、DAY等)方式來獲取到對應的時間頻率。我們使用YEAR和MONTH來對時間進行降採樣,注意與字符串連接一定要用“&”號。

=YEAR(B2)&"/"&MONTH(B2)

5.2.2 計算時間差

此步驟中需要用到DATEDIF函數,此公式常用於計算兩個日期之間的天數、月份、年數差,語法爲:=DATEDIF(起始時間,結束時間,時間頻率),常用的時間頻率參數有['Y','M','D'],分別對應年月日

=DATEDIF(E2,D2,"M")

5.2.3 重置月份差標籤

修改透視表的標籤並不方便,因此先重置月份差標籤,需要用到一個IF函數便可。具體語法:=IF(條件,符合條件時的操作,不符合條件時的操作)

=IF(F2=0,"首月","+"&F2&"月")

5.3 計算同期留存量和留存率

如果是office 2013及之後的版本,以上的數據已經足夠我們進行留存量的計算,可以直接跳過計算用戶出現次數環節。

5.3.1 計算每月中每個用戶出現的次數

這裏利用COUNTIFS函數,計算出“用戶暱稱”和“付款時間(重採樣)”均相同的次數,並取其倒數,讓當月無論該用戶出現多少次,最終都只會計算爲一次。即假設用戶當月付款5次,倒數後權重變爲1/5,求和後出現次數爲1。

COUNTIFS的語法爲:=COUNTIFS(區域A,條件A,區域B,條件B,....)

=COUNTIFS(A:A,A:A,D:D,D:D,E:E,E:E)

=1/H2

5.3.2 創建留存量數據透視表

針對wps及office2013以前的版本,我們已經計算了出現次數的倒數,只需要仿照前文“計算每個用戶首單時間”的步驟創建數據透視表,以“首單時間重採樣”作爲行,以“月份差標籤”作爲列,以“出現次數(倒數)”作爲值,並修改值字段設置中的計算類型爲求和即可。

而office 2013及之後的版本,我們在插入數據透視表時,需要注意勾選“將此數據添加到數據模型”

同樣以“首單時間重採樣”作爲行,以“月份差標籤”作爲列,但不同的是,我們可以直接以“客戶暱稱”作爲值,並在值字段設置當中,將計算類型設置爲“非重複計數”。

到此,我們留存量的透視圖便完成了,但格式看上去還是有點醜,我們手動拖動下行、列標籤的排序,最終獲得如下效果:

5.3.3 計算留存率

在值字段顯示方式當中並沒有找到我們想要的效果,因此我們在數據透視表下方選定一個區域,複製好行標籤和列標籤。通過公式“=C5/$B5”計算出留存率,並向右向下拖動公式便可完成

注:

  • B5爲2019年9月的首月留存量,C5爲2019年9月的+1月留存量

  • 分母需要將B列鎖定,否則在向右拖動公式時,分母會依次變爲C5、D5、E5

完美符合我們預期的結果!Excel版本的實現就到這裏便完成,接下來是門檻稍微高一點點的MySQL實現。

六、MySQL實現

MySQL的實現路徑與Excel的實現路徑非常相近,具體步驟爲:

  1. 導入數據

  2. 清洗數據:篩選訂單狀態爲“交易成功”的行

  3. 獲取首單時間

  4. 求月份偏移:求出月份差,並對首付時間降採樣

  5. 計算留存量:通過首付時間和月份差進行分組,求唯一的用戶id數

  6. 求留存率

6.1 導入數據

目前的數據的保存格式爲xlsx,我們需要先將數據導入到數據庫當中才能執行查詢。第一步選擇一個庫,右鍵選擇導入嚮導。

第二步選擇導入類型,我們直接選擇Excel文件即可。

第三步爲選擇數據源的路徑,我們找到對應的數據後,勾選需要導入的表。

完成前文的操作之後便可以點擊“>>”跳轉至最後的步驟,當然中間還有幾個調整數據的步驟,但此次數據十分工整,不需要進行額外操作。

到達下圖的界面,我們按照指引直接點擊“開始”即可,如導入成功,會在日誌欄中顯示Finished successfully,如下圖所示。

6.2 數據清洗

照舊先篩選出訂單狀態爲交易成功的行,並提取用戶暱稱、付款時間兩個字段。這裏我們稍微修改了列名,把`用戶暱稱`修改成`c_id`,`付款時間`修改爲`paytime`,`交易狀態`修改成了`status`。

我們後續的查詢都是基於篩選後的數據,因此這裏新建一個表sheet2去存儲查詢結果。

-- 步驟一:篩選訂單狀態爲”交易成功“的行,並輸出表sheet2:用戶暱稱[c_id]、付款時間[paytime]
CREATE table sheet2 as
SELECT c_id,paytime
FROM sheet1
WHERE `status`='交易成功';

6.3 計算首單時間

此步驟只需要對用戶暱稱進行groupby,再求最小值即可,不多贅述。

-- 步驟二:找出每個用戶的首單時間
SELECT c_id,min(paytime) f_time
FROM sheet2
GROUP BY c_id;

6.4 計算月份差,重採樣首付時間

此步驟中會涉及到兩個重要的函數:

  1. 與Excel類似,MySQL對時間戳重採樣也是用YEAR()、MONTH()等函數

  2. 用於計算日期差的TIMESTAMPDIFF,具體語法爲TIMESTAMPDIFF(頻率,起始時間,結束時間)

當然在計算月份差之前,需要以用戶名稱作爲依據,拼接用戶的首單時間。但由於數據量較大,拼接需要重複遍歷整個表很多遍,耗時很長。而當前查詢的結果並不是最終結果,我們只需要確保查詢語句沒有問題即可。因此我們引入分頁查詢(LIMIT語句)來限制查詢結果的行數,從而提高查詢效率。

-- 步驟三:求出月份差,對首付時間進行重採樣
SELECT
 a.c_id,
 b.f_time,
 TIMESTAMPDIFF(MONTH,b.f_time,a.paytime) m_diff,
 CONCAT(YEAR(b.f_time),"年",MONTH(b.f_time),"月") y_m
FROM sheet2 a
LEFT JOIN (
 SELECT c_id,min(paytime) f_time
 FROM sheet2
 GROUP BY c_id
--  LIMIT測試時用,爲了提升效率
 LIMIT 0,7000
) b on a.c_id=b.c_id
-- 同樣是爲了提升效率而使用
WHERE b.f_time is NOT NULL;

6.5 計算留存量

我們只需要將前面的三個步驟作爲子查詢,並以`首單時間`以及`月份差`作爲條件對數據進行分組,用DISTINCT篩選出唯一的`用戶ID`即可求出我們所需的留存量。這裏創建一個名爲cohort的表儲存查詢結果。

-- 步驟四:通過首付時間和月份差進行分組,求出唯一的用戶id數,並輸出爲表[cohort]
CREATE table cohort as
SELECT c.y_m "首付月份",c.m_diff"月份差",COUNT(DISTINCT c.c_id) "留存量"
FROM (
SELECT
 a.c_id,
 b.f_time,
 TIMESTAMPDIFF(MONTH,b.f_time,a.paytime) m_diff,
 CONCAT(YEAR(b.f_time),"年",MONTH(b.f_time),"月") y_m
from sheet2 a
LEFT JOIN (
 SELECT c_id,min(paytime) f_time
 FROM sheet2
 GROUP BY c_id
) b on a.c_id=b.c_id
-- 爲了提升效率而使用
WHERE b.f_time is NOT NULL
) c
GROUP BY c.y_m,c.m_diff;

查詢結果如下。相比於步驟三,我們這裏刪除了用於分頁查詢的LIMIT語句,但依然保留了WHERE b.f_time is NOT NULL。這裏的where語句並沒有篩選任何一行,但有無這一句的查詢效率相差非常大,分別爲0.739s和125.649s。這裏涉及到SQL優化的問題,有機會以後專門整理一篇文章分享給各位。

6.6 計算留存率

我們有了留存量的表格,計算留存率便非常容易,只要讓每一期的留存率都除以首月的留存率即可。

-- 步驟五:計算留存率(基礎版)
SELECT c.`首付月份`,CONCAT(ROUND((c.`留存量`/m.`留存量`)*100,2),"%") 留存率
FROM cohort c
LEFT JOIN (
 SELECT 首付月份,留存量
 FROM cohort
 where `月份差`=0
) m
on c.`首付月份`=m.`首付月份`;

留存率結果如上圖,但結果並不利於觀察和分析,因此接下來的進階版將通過case when語句,加入億點細節來優化下展示格式。

-- 步驟五:計算留存率(進階版)
SELECT
 n.`首付月份`,
 AVG(n.`留存量`) "本月新增",
 CONCAT(sum(n.`+1月`),"%") "+1月",
 CONCAT(sum(n.`+2月`),"%") "+2月",
 CONCAT(sum(n.`+3月`),"%") "+3月",
 CONCAT(sum(n.`+4月`),"%") "+4月",
 CONCAT(sum(n.`+5月`),"%") "+5月"
FROM(
 # 一級子查詢:轉置表格,將月份差作爲列名
 SELECT
  a.`首付月份`,
  a.`留存量`,
  CASE a.`月份差` when 1 THEN a.`留存率` ELSE 0 END "+1月",
  CASE a.`月份差` when 2 THEN a.`留存率` ELSE 0 END "+2月",
  CASE a.`月份差` when 3 THEN a.`留存率` ELSE 0 END "+3月",
  CASE a.`月份差` when 4 THEN a.`留存率` ELSE 0 END "+4月",
  CASE a.`月份差` when 5 THEN a.`留存率` ELSE 0 END "+5月"
 FROM(
  # 二級子查詢:計算留存率
  SELECT a.`首付月份`,b.`留存量`,a.`月份差`,ROUND((a.`留存量`/b.`留存量`)*100,2) 留存率
  FROM cohort a
  LEFT JOIN (
   # 三級子查詢:查詢首月用戶量
   SELECT `首付月份`,`留存量`
   FROM cohort
   WHERE cohort.`月份差`=0
 ) b
 on a.`首付月份`=b.`首付月份`
 ) a
) n
GROUP BY n.`首付月份`;

正如“分析方法確定”環節中提及,Excel中通過自然月去劃分月份的偏移量,而MySQL中則直接將付款時間和首單時間相減。我們使用的TIMESTAMPDIFF函數的邏輯爲結束日期的DAY參數大於等於起始日期的DAY參數時,月份差纔會+N。即:

  • 起始日期爲9月30日,終止日期大於等於10月30日時,月份差纔不爲0。

  • 起始日期爲10月31日,終止日期大於等於12月1日時,月份差纔不爲0。

  • 起始日期爲1月30或31日,終止日期大於等於3月1日時,月份差纔不爲0,平/閏年一樣。

對比可知,算法1中留存率會出現小幅度的回升,但在算法2則隨時間增加而遞減。由此可知,不同的計算標準對結果影響非常大,可能會造成誤判,因此數據分析中確認標準非常重要

七、Python實現

作爲壓軸,肯定是路子野、效率高、操作騷的Python。得益於pandas強大的分組功能及非常多的奇技淫巧,Python的實現相比於Excel或MySQL會更加簡單,但實現路徑會比較抽象,需要注入一點想象力。按慣例先盤實現思路:

  1. 數據清洗:刪除訂單狀態爲”交易失敗“的行

  2. 拼接首單時間:計算每個用戶首單時間,並拼接爲新的dataframe

  3. 求留存量:對數據分組,並求唯一的客戶暱稱數

  4. 求留存率:用首月留存量除整個留存量的dataframe

7.1 數據清洗

此步驟只需要調用drop函數即可完成刪除,難度不大,核心是找到訂單狀態爲“交易失敗”的所在行的行索引。

df.drop(index=df[df['訂單狀態'] == '交易失敗'].index, axis=1, inplace=True)

7.2 拼接首單時間

調用分組聚合函數groupby以及數據拼接函數merge便能完成我們的需求,都是常規操作

df_f = df.groupby(by='客戶暱稱')['付款時間'].min().to_frame(name='首單時間')
df_f.reset_index(inplace=True)

# 合併新的dataframe,包含客戶暱稱,付款時間,首單時間
df_f = df[['客戶暱稱', '付款時間']].merge(df_f)

7.3 計算留存量

接下來就是見證騷操作的時刻了。在pandas的分組聚合當中,對時間戳進行重採樣不要太簡單,只需要修改freq參數即可。核心思路:

  • 利用groupby函數對首單時間和付款時間進行分組,獲得複合索引的series

  • 利用pd.Grouper對首單時間和付款時間進行重採樣

  • 利用nunique函數求不重複值個數

  • 利用unstack函數將複合索引的series轉爲dataframe

# 通過首單時間及付款時間進行分組,獲得每個時間段的不重複客戶數量
df_f = df_f.groupby(by=[pd.Grouper(key='首單時間', freq='m'), pd.Grouper(key='付款時間', freq='m')])['客戶暱稱'].nunique()

# 將複合索引的series轉置爲dataframe
df_f = df_f.unstack()

獲得的結果如上圖。如果有看Excel或MySQL實現方式的看官可能有會有疑問,爲什麼python不用計算月份差而其他兩種需要。那是因爲這種分組方式,首月用戶量都分佈在表格的對角線上,在Excel的數據透視表或者MySQL當中,等差地移動單元格並不是一件容易的事,但對於Python來說,不過是一個for循環。

for i in range(len(df_f.index)):
 df_f.iloc[i] = df_f.iloc[i].shift(periods=-i)
  
# 重置columns
df_f.columns = ['本月新增', '+1月', '+2月', '+3月', '+4月', '+5月']

shift函數常用於移動dataframe或series,具體參數如下:

  • axis:針對dataframe:{0:"向下移動" , 1:"向右移動"},針對series:向下移動

  • periods:移動的步長,當periods爲負時,向上/左移動

  • fill_value:補充NaN的值

得到如下結果

7.4 計算留存率

儘管pandas非常強大,但此步驟中,如通過df_f/df_f[‘首月’]計算,結果是全爲NaN的dataframe。不過我們可以使用apply函數遍歷dataframe來實現。

df_1 = df_f.apply(count_per, axis=0, args=(df_f['本月新增'],))
df_1['本月新增']=df_f['本月新增']

def count_per(s, dx):
    a=[f'{i}%' if str(i)!='nan' else 0 for i in round((s / dx) * 100, 2)]
    return a

作爲pandas中最好用的函數之一,apply的詳細用法各位參考官方文檔即可,這裏僅提三點注意事項:

  1. 在apply中調用的函數不需要加括號,僅提供函數名即可

  2. 向apply調用的函數傳遞變量,只需賦值給args,如果僅傳遞一個變量,要在變量後加上 “,”號

  3. 調用的函數當中第0個參數由self提供,從第一個變量開始纔是args中的變量,即上面函數中,dx對應的是df_f['本月新增']

獲得結果如下,完美完成任務:

八、覆盤總結

先回顧下同期羣分析的重點

  • 同期羣分析指將用戶進行同期羣劃分後,對比不同同期羣組用戶的相同指標的分析方法

  • 同期羣分析是產品數據分析的核心,能細緻地監控用戶行爲,衡量用戶價值

  • 時間的劃分標準對分析結果影響很大,確定標準非常重要

最後總結下本次ESP實現方式中分別涉及到的重要知識點

工具重要知識點
Excel- 數據透視表
- VLOOKUP函數
- 時間重採樣函數:YEAR、MONTH
- 時間差函數:DATEDIF
- 條件函數:IF、COUNTIFS
MySQL- 時間重採樣函數:YEAR、MONTH
- 時間差函數:TIMESTAMPDIFF
- 流程控制函數:CASE WHEN
Python- 分組api:pd.Grouper()
- 不重複計數:nunique()
- 元素移動:shift()
- apply()

那麼本次的分享到這裏便結束了。至於同期羣分析如何應用到實際業務問題中,我們留到下一篇商業分析實戰再詳細講解。

如果寫出來的話,一定不會鴿,一定不會~鴿!

我是HeoiJin,不要期待有下篇~

參考資料

  1. Python數據分析實戰 | 經典的同期羣分析(附實戰數據和代碼)

  2. 數據分析裏經常聽到的同期羣,到底是個啥?

  3. 同期羣分析(Cohort Analysis)

完整代碼及數據集

github:https://github.com/heoijin/BAproject

曲奇雲:https://quqi.gblhgk.com/s/4137601/W32878QYb6YoC3sq

百度雲:

  • 鏈接: https://pan.baidu.com/s/1JItYwQ5_PrDyg3xoZLktwA

  • 提取碼: enp7

奶牛快傳:https://alltodata.cowtransfer.com/s/d0644dd3998046

(推薦!不限速!不需要登錄)

後臺回覆「進羣」,加入讀者交流羣~

點擊積分,瞭解積分規則~

下面書籍限時300積分。大家給本文點擊在看留言點讚的前三名分別200、100、50積分,可以兌換本書,也可兌換積分商城任意書籍

朱小五

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章