案例-基于自动节点树的数据异常原因下探分析

# - 依赖库:datetime、numpy、pandas、graphviz
# - 程序输入:advertising_data.csv
# - 程序输出:打印输出并保存节点树图change_analysis_tree.png

# 程序

# 导入库

import datetime

import numpy as np
import pandas as pd
from graphviz import Digraph  # 画图用库

# 读取数据

raw_data = pd.read_csv('advertising_data.csv')

# 数据审查

# 数据概览
print('{:*^60}'.format('Data overview:'), '\n', raw_data.tail(2))  # 打印原始数据后2条
print('{:*^60}'.format('Data dtypes:'), '\n', raw_data.dtypes)  # 数据类型

# 缺失值审查
na_cols = raw_data.isnull().any(axis=0)  # 查看每一列是否具有缺失值
print('{:*^60}'.format('NA Cols:'))
print(na_cols[na_cols] == True)  # 查看具有缺失值的列
print('Total NA lines is: {0}'.format(raw_data.isnull().any(axis=1).sum()))  # 查看具有缺失值的行总记录数

# 数据预处理

# 替换字符为0然后转换为整数型
raw_data['visit'] = raw_data['visit'].replace('-', 0).astype(np.int64)
print('{:*^60}'.format('Data overview:'))
print(raw_data.tail(2))

# 将字符串转换为日期格式
raw_data['date'] = pd.to_datetime(raw_data['date'], format='%Y/%m/%d')
print('{:*^60}'.format('Data dtypes:'))
print(raw_data.dtypes)

# 计算整体波动量

day_summary = raw_data.iloc[:, -1].groupby(raw_data.iloc[:, 0]).sum()  # 按天求和汇总
day_change_value = day_summary.diff(1).rename('change')  # 通过差分求平移1天后的变化量
day_change_rate = (day_change_value.shift(-1) / day_summary).round(3).rename('change_rate').shift(1)  # 求相对昨天的环比变化率
day_summary_total = pd.concat((day_summary, day_change_value, day_change_rate), axis=1)  # 整合为完整数据框
print('{:*^60}'.format('Data change summary:'))
print(day_summary_total.head())

# 指定日期自动下探分解

# 定义变量
# 分解对象
dimension_list = ['source', 'site', 'channel', 'media']  # 指定要分析的维度:4个层级
# 日期对象
the_day = pd.datetime(2018, 6, 7)  # 指定要分析的日期
previous_day = the_day - datetime.timedelta(1)  # 自动获取前1天日期
# 日期列名
day_col1 = datetime.datetime.strftime(the_day, '%Y-%m-%d')
day_col2 = datetime.datetime.strftime(previous_day, '%Y-%m-%d')
# 数据对象
the_data = raw_data[raw_data['date'] == the_day].rename(columns={'visit': day_col1})  # 获得指定日期数据
previous_data = raw_data[raw_data['date'] == previous_day].rename(columns={'visit': day_col2})  # 获得前1天日期数据

# 数据合并计算
# 合并两天的数据
data_merge = the_data.iloc[:, 1:].merge(previous_data.iloc[:, 1:], on=dimension_list, how='outer')
# 替换没有匹配的数据为0
data_merge = data_merge.fillna(0)
# 计算相对昨天的环比变化率
data_merge['change'] = data_merge[day_col1] - data_merge[day_col2]  # 变化量
data_merge.head()
# 整体对象
visit, change, change_rate = day_summary_total[day_summary_total.index == the_day].values[0]
top_nodes = {'source': 'all site', 'change': change, 'change_rate': change_rate}

# 自动节点分解
main_nodes = []  # 主节点
other_nodes = []  # 其他节点
hidden_nodes = []  # 潜在节点
main_edges = []  # 主边
other_edges = []  # 其他边
dim_copy = ['source', 'site', 'channel', 'media', day_col2, 'change']
for ind, dimension in enumerate(dimension_list):  # 遍历每个维度
    each_data = data_merge[dim_copy[ind:]]  # 筛选数据
    each_merge = each_data.groupby([dimension], as_index=False)[day_col2, 'change'].sum()  # 计算变化量
    each_merge = each_merge.sort_values(['change'])  # 排序
    each_merge['each_change_rate'] = each_merge['change'] / each_merge[day_col2]  # 环比变化率
    each_merge = each_merge.drop(day_col2, axis=1)  # 丢弃当日visit数值列
    change_all = each_merge.sum().iloc[1]  # 总变化量
    if change_all < 0:  # 下降
        # node
        main_values = each_merge.iloc[0].tolist()  # 主因子节点
        main_nodes.append(main_values)
        other_nodes.append([f'{dimension}-others', change_all - main_values[1], 1 - main_values[2]])
        hidden_nodes.append(each_merge.iloc[-1].tolist())
        # 数据过滤
        data_merge = each_data[each_data[dimension] == each_merge.iloc[0].iloc[0]]
    else:  # 上升
        # node
        main_values = each_merge.iloc[-1].tolist()  # 其他因子节点
        main_nodes.append(main_values)
        other_nodes.append([f'{dimension}-others', change_all - main_values[1], 1 - main_values[2]])
        hidden_nodes.append(each_merge.iloc[0].tolist())
        # 数据过滤
        data_merge = each_data[each_data[dimension] == each_merge.iloc[-1].iloc[0]]
        # edge
    edge_values = main_values[1] / float(change_all)
    main_edges.append(edge_values)
    other_edges.append(1 - edge_values)

# 画图展示自动下探结果

# 定义各个节点的样式
node_style = '<<table border="0"><tr><td width="20"><table border="1" cellspacing="0" VALIGN="MIDDLE"><tr><td bgcolor="{0}"><font color="{1}"><B>{2}</B></font></td></tr><tr><td>环比变化量:{3:d}</td></tr><tr><td>环比变化率:{4:.2%}</td></tr></table></td></tr></table>>'
edge_style = '<<table border="0"><tr><td><table border="0" cellspacing="0" VALIGN="MIDDLE" bgcolor="#ffffff"><tr><td>{0}</td></tr><tr><td>贡献率:{1:.0%}</td></tr></table></td></tr></table>>'
attr_node = {'fontname': "SimHei", 'shape': 'box', 'penwidth': '0'}  # 定义node节点样式
attr_edge = {'fontname': "SimHei"}  # 定义edge节点样式
attr_graph = {'fontname': "SimHei", 'splines': 'ortho', 'nodesep': '2'}  # Graph的总体样式

# 定义左侧父级图
parent_dot = Digraph(format='png', graph_attr=attr_graph, node_attr={'shape': 'plaintext', 'fontname': 'SimHei'})
features = ['全站', 'source', 'site', 'channel', 'media']
parent_edge = [(features[i], features[i + 1]) for i in range(len(features) - 1)]
parent_dot.edges(parent_edge)

# 定义右侧子级图
child_dot = Digraph(node_attr=attr_node, edge_attr=attr_edge)  # 创建有向图
for tree_depth in range(len(main_nodes)):  # 循环读取每一层
    split_node_left = main_nodes[tree_depth]
    split_node_right = other_nodes[tree_depth]
    split_node_hidden = hidden_nodes[tree_depth]

    if tree_depth == 0:
        # 增加顶部节点
        node_name = '汇总值'
        node_top_label = node_style.format('black', "white", node_name,
                                           int(top_nodes['change']),
                                           top_nodes['change_rate'])  # 分别获取顶部节点名称、变化量和变化率
        child_dot.node('汇总值', label=node_top_label)  # 增加顶部节点
    else:
        node_name = main_nodes[tree_depth - 1][0]  # 将上级左侧分裂节点作为下级节点的source

    # 增加node信息
    node_label_left = node_style.format("#184da5", "white", split_node_left[0],
                                        int(split_node_left[1]),
                                        split_node_left[2])  # 左侧节点显示的信息
    node_label_right = node_style.format("#d3d3d3", "black", split_node_right[0],
                                         int(split_node_right[1]),
                                         split_node_right[2])  # 右侧节点显示的信息
    node_label_hidden = node_style.format("#72a518", "black", split_node_hidden[0],
                                          int(split_node_hidden[1]),
                                          split_node_hidden[2])  # 潜在节点显示的信息
    # 增加边信息
    edge_label_left = edge_style.format('主因子', main_edges[tree_depth])  # 左侧边的标签信息
    edge_label_right = edge_style.format('其他因子', other_edges[tree_depth])  # 右侧边的标签信息

    # 节点和边画图
    child_dot.node(split_node_left[0], label=node_label_left)  # 增加左侧节点
    child_dot.node(split_node_right[0], label=node_label_right)  # 增加右侧节点
    child_dot.node(split_node_hidden[0], label=node_label_hidden)  # 增加隐藏节点
    child_dot.edge(node_name, split_node_left[0], label=edge_label_left)  # 增加左侧边
    child_dot.edge(node_name, split_node_right[0], label=edge_label_right)  # 增加右侧边
    child_dot.edge(split_node_right[0], split_node_hidden[0], label='潜在因子')  # 增加隐藏节点边

parent_dot.subgraph(child_dot)
parent_dot.view('change_analysis_tree')  # 展示图形结果

注:数据文件链接: https://pan.baidu.com/s/16RsmM7KEiJkazBzKW18Q4g 密码: cuck

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值