python資料分析:流量資料化運營(中)——流量資料波動原因下探分析
從細分到多層下鑽資料分析
細分是網站分析的基本方法,也是資料分析的基本思路。細分分析的過程是對整體資料進行層層拆分,然後找到影響整體的區域性因素。
步驟1:全站流量按來源模組可細分為廣告、SEM、SEO和直接輸入(假設只有4個模組)。細分發現廣告是網站流量的主要來源(昨日訪問量佔比82%),訪問量增長2194,比例為67%,說明了廣告是網站訪問量增長的主要驅動因素。
步驟2:對廣告模組做進一步細分,發現其中主要增長模組為Sina,該模組昨日訪問量佔比79%,環比前日增長1990,比例為85%。如果該模組有不同的位置,還可以做進一步細分。
至此已經找到了昨日網站訪問量增長的主要原因是Sina來源流量增長,此時可直接找到Sina模組的業務負責人進行溝通進一步原因。
案例背景
日報、週報、月報等常規性報告是各個公司的基礎資料支援形式。在日常報告中,經常會出現很多異常波動的指標,需要分析師找到異常波動的影響因素。但在尋找主要因素時由於需要下探的層級較多,實施起來會非常費時費力。以大型公司的廣告投放渠道為例,可能包括以下層級:
- 一級渠道包括SEM、AD、CPS、Social、導航等;
- 二級渠道以SEM為基準包括百度、谷歌、360等;
- 三級渠道以百度為基準包括關鍵字、網盟等;
- 四級渠道以關鍵字為基準包括不同的廣告計劃;
- 五級渠道以廣告計劃為基準可以細分到不同的廣告組;
- 六級渠道以廣告組為基礎可以細分到不同的關鍵字。
本案例介紹了一個自動化細分找到主要影響因素的方法——基於自動節點樹的資料異常原因下探分析方法,該方法的實施思路是:找到每個層級上影響最大的因素並依次做下一因素的細分,直至最後一個因素。具體過程如下:
- 統計全站在一定週期內、特定指標下的資料環比變化量和環比變化率。
- 指定要分析日期並獲得該日期及其前1天的資料。
- 以全站資料為基準,下探第一層級維度並對指定日期和其前1天的資料做分類彙總。
- 計算第一層級維度下分類彙總後的兩天資料的差值並得到環比變化量和環比變化率。
- 對第一層級維度下的變化量排序,並分別獲得環比變化量最大和最小情況下的維度名稱、變化量和變化率。
- 計算下一層級變化量與上一層級變化量的比值,變化量最大值和最小值的比例將被定義為正向貢獻率和負向貢獻率。
- 迴圈上述步驟,直至所有層級都計算完成。
- 使用樹形圖展示所有層級下的變化量最大和最小的維度資訊包括維度名稱、環比變化量、環比變化率、貢獻率等資訊。
案例資料
以下是本資料集的6個維度的詳細說明:
- date:日期,格式是YYYY/MM/DD。
- source:流量來源一級分類,來源於業務部門的定義。
- site:流量來源二級分類,來源於業務部門的定義。
- channel:流量來源三級分類,來源於業務部門的定義。
- media:流量來源四級分類,來源於業務部門的定義。
- visit:訪問量。
python程式碼
import datetime
import pandas as pd
import numpy as np
from graphviz import Digraph # 畫圖用庫
# 載入資料
df = pd.read_csv('https://raw.githubusercontent.com/ffzs/dataset/master/advertising_data.csv')
# 顯示部分資料
df.head()
# 資料型別
df.dtypes
‘‘’
date object
source object
site object
channel object
media object
visit object
dtype: object
‘’’
# 檢視缺失值
df.isna().sum()
‘’‘
date 0
source 0
site 0
channel 0
media 0
visit 0
dtype: int64
’‘’
根據上述的資料審車校驗結果,需要處理的內容包括:
- 將date列轉換為日期型,便於後期基於資料做篩選以及日期計算。
- 將visit列中的“-”進行轉換。由於從網站分析工具匯出時,“-”代表的是沒有資料,因此這裡轉換為0。
# date列資料轉化為日期型資料
df['date'] = df.date.astype('datetime64[ns]')
# 將visit列中的“-”轉換為0
df['visit'] = df.visit.replace('-', 0).astype('int64')
這裡先將每天的資料與其前1天的資料做環比變化統計:
# 獲得每天visit的彙總
day_summary = df.iloc[:,-1].groupby(df.date).sum()
# 通過差分求平移一天後的變化
day_change_value = day_summary.diff(1).rename('change')
# 求變化率
day_change_rate = (day_change_value / day_summary).round(3).rename('change_rate')
# 合併
df_visit = pd.concat((day_summary, day_change_value, day_change_rate), axis=1)
# 展示
df_visit.head()
# 趨勢觀察
df_visit.visit.plot()
指定某一天進行分析:
# 指定要分析的日期
the_day = pd.datetime(2017, 6, 7)
# 獲取前一天日期
previous_day = the_day - datetime.timedelta(1)
# 獲取指定日期資料
the_day_tmp = df[df.date == the_day].rename(columns={'visit': the_day})
# 獲取前一天資料
previous_day_tmp = df[df.date == previous_day].rename(columns={'visit': previous_day})
# 指定需要分析4個維度
dimension_list = df.columns.tolist()[1:-1]
# 每層分裂節點名稱列表
split_node_list = ['全站']
# 每層分裂節點對應的總變化量
change_list = []
# 每層最大增長貢獻最大的1個維度
increase_node_list = []
# 每層最小增長貢獻最大的一個維度
decrease_node_list = []
# 遍歷每一個維度
for dimension in dimension_list:
# 獲取指定日期的特定維度和訪問量
the_day_merge = the_day_tmp[[dimension, the_day]]
# 獲取前一天的特定維度和訪問量
previous_day_merge = previous_day_tmp[[dimension, previous_day]]
# 對指定日期的特定維度彙總
the_day_groupby = the_day_merge.groupby(dimension).sum()
# 對前一天的維度彙總
previous_day_groupby = previous_day_merge.groupby(dimension).sum()
# 講兩天的資料合併
merge_data = the_day_groupby.join(previous_day_groupby, how='outer')
# 將缺失值替換為0
merge_data = merge_data.fillna(0)
# 計算環比變化量
merge_data['change'] = merge_data[the_day] - merge_data[previous_day]
# 計算環比變化率
merge_data['change_rate'] = merge_data['change'] / merge_data[previous_day]
# 獲取分裂節點的總變化值
total_change = merge_data.change.sum()
# 將變化值加入列表
change_list.append(total_change)
# 安環比變化量正向排序
merge_data.sort_values('change', inplace=True)
# 獲得增長變化量最大節點名稱
max_increase_node = merge_data.index[-1]
# 獲得最大值節點變化量以及變化比例
max_value, max_rate = merge_data.loc[max_increase_node][2:4]
# 將最大值資訊追加到列表
increase_node_list.append([max_increase_node, int(max_value), max_rate])
# 獲得增長變化量最小值節點名稱
min_increase_node = merge_data.index[0]
# 獲得最小值節點變化量以及變化比例
min_value, min_rate = merge_data.loc[min_increase_node][2:4]
# 將最小值資訊追加到列表
decrease_node_list.append([min_increase_node, int(min_value), min_rate])
# 判斷增長方向
if total_change >= 0:
# 將分裂節點定義為增長最大值節點
split_node_list.append(max_increase_node)
# 通過分裂節點的個數判斷所處分裂層級
rules_len = len(split_node_list)
# 按照相應的維度過濾出指定日期符合最大節點條件的資料
the_day_tmp = the_day_tmp[the_day_tmp[dimension_list[rules_len-2]] == max_increase_node]
# 按照相應的維度過濾出前一天符合最大節點條件的資料
previous_day_tmp = previous_day_tmp[previous_day_tmp[dimension_list[rules_len-2]] == max_increase_node]
else:
split_node_list.append(min_increase_node)
# 通過分裂節點的個數判斷所處分裂層級
rules_len = len(split_node_list)
# 按照相應的維度過濾出指定日期符合最大節點條件的資料
the_day_tmp = the_day_tmp[the_day_tmp[dimension_list[rules_len-2]] == min_increase_node]
# 按照相應的維度過濾出前一天符合最大節點條件的資料
previous_day_tmp = previous_day_tmp[previous_day_tmp[dimension_list[rules_len-2]] == min_increase_node]
使用graphviz繪製關係圖:
#### 作圖 ####
node_style = {'fontname': "SimSun", 'shape': 'box'} # 定義node節點樣式
edge_style = {'fontname': "SimHei", 'fontsize': '11'} # 定義edge節點樣式
top_node_style = '<<table><tr><td bgcolor="black"><font color="white">{0}</font></td></tr><tr><td>環比變化量:{1:d}</td></tr><tr><td>環比變化率:{2:.0%}</td></tr></table>>' # 定義頂部node節點標籤樣式
left_node_style = '<<table><tr><td bgcolor="chartreuse"><font color="black">{0}</font></td></tr><tr><td>環比變化量:{1}</td></tr><tr><td>環比變化率:{2:.0%}</td></tr></table>>' # 定義左側node節點標籤樣式
right_node_style = '<<table><tr><td bgcolor="lightblue"><font color="black">{0}</font></td></tr><tr><td>環比變化量:{1}</td></tr><tr><td>環比變化率:{2:.0%}</td></tr></table>>' # 定義右側node節點標籤樣式
dot = Digraph(format='png', node_attr=node_style, edge_attr=edge_style) # 建立有向圖
for i in range(4): # 迴圈讀取每一層
node_name = split_node_list[i] # 獲得分裂節點名稱
node_left, max_value, max_rate = increase_node_list[i] # 獲得增長最大值名稱、變化量和變化率
node_right, min_value, min_rate = decrease_node_list[i] # 獲得增長最小值名稱、變化量和變化率
node_change = change_list[i] # 獲得分裂節點的總變化量-非分裂節點變化量
node_label_left = left_node_style.format(node_left, max_value, max_rate) # 左側節點顯示的資訊
node_label_right = right_node_style.format(node_right, min_value, min_rate) # 右側節點顯示的資訊
if i == 0: # 如果是頂部節點,則單獨增加頂部節點資訊
day_data = df_visit[df_visit.index == the_day] # 獲得頂部節點的資料
former_data = day_data.ix[0, 1] # 獲得全站總變化量
node_lable = top_node_style.format(node_name, int(former_data), day_data.ix[0, 2]) # 分別獲取頂部節點名稱、變化量和變化率
dot.node(node_name, label=node_lable) # 增加頂部節點
contribution_rate_1 = float(max_value) / former_data # 獲得左側變化量貢獻率
contribution_rate_2 = float(min_value) / former_data # 獲得右側變化量貢獻率
if node_change >= 0: # 如果為增長,則左側為正向
edge_lablel_left = '正向貢獻率:{0:.0%}'.format(contribution_rate_1) # 左側邊的標籤資訊
edge_lablel_right = '反向貢獻率:{0:.0%}'.format(contribution_rate_2) # 右側邊的標籤資訊
former_data = max_value # 獲得上一層級變化量最大值
else: # 如果為下降,則右側為正向
edge_lablel_left = '反向貢獻率:{0:.0%}'.format(contribution_rate_1) # 左側邊的標籤資訊
edge_lablel_right = '正向貢獻率:{0:.0%}'.format(contribution_rate_2) # 右側邊的標籤資訊
former_data = min_value # 獲得上一層級變化量最大值
dot.node(node_left, label=node_label_left) # 增加左側節點
dot.node(node_right, label=node_label_right) # 增加右側節點
dot.edge(node_name, node_left, label=edge_lablel_left, color='chartreuse') # 增加左側邊
dot.edge(node_name, node_right, label=edge_lablel_right, color='lightblue') # 增加右側邊
dot.view('change summary') # 展示圖形結果
結論
全站訪問量下降18455,下降比例達到23%,主要的source源是CRM,其下降量為17591, “貢獻”了95%的主要因素;而導致CRM下降的主要site源是準會員,其下降量為17575, “貢獻了”幾乎100%的下降因素;再進一步細分,在影響準會員的channel中,電視源的下降量達到19090, “貢獻”了109%的比例,而導致電視流量下降的主要media源是APP,其貢獻了19024的下降流量,比例幾乎是100%。與此同時,某些來源渠道的流量與全站的下降趨勢相反,呈現良好的增長趨勢,這在全站的下降主要因子中表現良好,包括source源中的公眾號流量環比增長2444,增長率達到171%; s準會員中的商城部分的流量增長1378,增長率為29%。
在上述結論中,針對每個節點(含分裂節點和非分裂節點)我們都有兩個方向的參照:
- 橫向因子,即查詢哪些特徵對於上一層級的變化有主要的正向和負向貢獻,這是貢獻率的來源;
- 縱向因子,即查詢每個節點本身相對前1天的環比變化率,這是其本身隨著時間的變化特徵,能有效瞭解其自身波動水平。
補充
案例使用的graphviz是一個非常強大的用於展示覆雜關係庫。該庫還有很多我們可以應用到的複雜場景,例如:
- 基於網路轉發的傳播關係圖;
- 個人關係聯絡圖;
- 基於有時間序列的流程圖;
- 網路拓撲關係圖;
- 資訊流和事件流圖。
案例中分裂尋找的是變化量(也意味著變化率)最大的節點,如果有課題需要也可以將變化率或貢獻度指定出來,在計算的時候按照指定的貢獻率提取出一系列(而不是一個)因子。或者,可以依據業務手工分析的需求,沿著指定維度做層層下探,其實只是在維度迴圈時從預設維度變為根據系統選擇傳值而已。整個的實現思路,已經跟決策樹的實現思想比較接近了,如果讀者興趣可以參照決策樹的實現和優化思路。
參考:
《python資料分析與資料化運營》 宋天龍