Python图形化客户关联关系

  • 图形用户界面展示客户关联关系
  • 一.创作背景
  • 二.设计思路
  • 三.导入所需要的库
  • 四.图形用户界面代码实现
  • 初始用户界面
  • 查询用户界面
  • 图形用户界面
  • 其他界面
  • 代码实现
  • 五.客户关联关系判断
  • 数据获取并构建图
  • 关系判断并返回结果信息
  • 六.客户关联图形绘制
  • 七.代码的简单封装
  • 八.总结


图形用户界面展示客户关联关系

一.创作背景

金融行业反洗钱作业中会有自检自查的工作,例如通过下表作为交易数据判断:

1.张三和周扒皮之间是否有交易关联,如何关联

2.小利和小泉之间是否有交易关联,是否有反向关联

3…假设有10万条数据,找出A和B的关系

python供应商画像 python客户画像_关联关系

二.设计思路

  • pandas 数据处理,整合成可用于图计算的数据格式;
  • networkx 构建图形和计算,其中关键方法有DiGraph构建有向图,has_path判断是否连通, shortest_path_length 获取最短路径长度 ,all_shortest_paths 最短路径获取,draw 图形设置;
  • tkinter 设置用户界面;
  • Pyinstaller 封装为exe文件。

三.导入所需要的库

import 导入库,其中 tkinter用于图形用户界面(GUI)设计,networkx用于图计算是核心工具包,matplotlib用于画图,其他均为常规库在此不赘述。

import tkinter as tk
import tkinter.messagebox as tm
import pandas as pd
import networkx as nx
from collections import Counter
import os
import matplotlib.pyplot as plt
import numpy as np
import warnings

warnings.filterwarnings('ignore')

四.图形用户界面代码实现

此处建立类旨在构建可复用的图形化界面,按键调用函数可根据不同需求任意修改。本文包含了button_query button_draw button_clearn 三个按键对于的操作,可作为参考。

初始用户界面

  1. 两个输入框及框名显示
  2. 三个按键(查询、图形、清空)
  3. 结果返回(放置在输入框上方)

python供应商画像 python客户画像_后端_02

查询用户界面

根据输入不同,查询结果及提示各有差异,具体展示可按照自己喜好设置

python供应商画像 python客户画像_开发语言_03

图形用户界面

用于一线人员的工具必须注意隐私保护,防止因个人疏忽导致信息泄露,除源节点及目标节点外其他节点信息用数字代替(且保证数字不会指向任何固定用户)

python供应商画像 python客户画像_关联关系_04

其他界面

其他界面及展示如有需要可自行尝试

代码实现

注释详细,不再赘述

class TkSet:
    """
        窗口设置
    """

    def __init__(self, init_window_name):
        """窗口大小及位置设置"""
        self.init_window_name = init_window_name
        # 得到屏幕宽度
        self.sw = init_window_name.winfo_screenwidth()
        # 得到屏幕高度
        self.sh = init_window_name.winfo_screenheight()
        # 窗口宽 和 高
        self.ww = 320
        self.wh = 120
        # 窗口位于屏幕x轴 和 y轴
        self.x = int((self.sw - self.ww) / 2.5)
        self.y = int((self.sh - self.wh) / 3.5)

    def set_window(self):
        """窗口内容设置"""
        self.init_window_name.title("关联关系分析")
        # 位置设置 窗口宽x窗口高+窗口位于屏幕x轴+窗口位于屏幕y轴
        self.init_window_name.geometry('{}x{}+{}+{}'.format(self.ww, self.wh, self.x, self.y))

        # 查询结果提示
        self.query_result = tk.Label(self.init_window_name, text='')
        self.query_result.grid(row=0, columnspan=2)  # 跨越两列显示

        # 第一行起始节点输入框
        self.source_tips = tk.Label(self.init_window_name, text='起始端名称:')
        self.source_tips.grid(row=1, sticky=tk.W)
        self.source = tk.Entry(self.init_window_name)
        self.source.grid(row=1, column=1, sticky=tk.E, padx=3)

        # 第二行目标节点输入框
        self.target_tips = tk.Label(self.init_window_name, text='结束端名称:')
        self.target_tips.grid(row=2, sticky=tk.E)
        self.target = tk.Entry(self.init_window_name)
        self.target.grid(row=2, column=1, sticky=tk.E, padx=3)

        # 第三行查询按钮
        # Frame框架控件;在屏幕上显示一个矩形区域,多用来作为容器
        self.f_btn = tk.Frame(self.init_window_name)
        self.b_query = tk.Button(self.f_btn, text='查询', width=6, command=self.button_query)
        self.b_query.grid(row=0, column=0)
        # self.b_cancel = tk.Button(self.f_btn, text='取消', width=6, command=self.init_window_name.quit)
        # self.b_cancel.grid(row=0, column=1)
        self.b_draw = tk.Button(self.f_btn, text='图形', width=6, command=self.button_draw)
        self.b_draw.grid(row=0, column=1)
        self.b_clearn = tk.Button(self.f_btn, text='清空', width=6, command=self.button_clearn)
        self.b_clearn.grid(row=0, column=2)
        self.f_btn.grid(row=3, columnspan=2, pady=10)

        self.init_window_name.mainloop()

    def button_query(self):
        """按键返回信息设置"""
        # 首先:输入的信息获取
        self.source_node = self.source.get()
        self.target_node = self.target.get()
        # 然后:查询结果显示
        try:
            self.query_result['text'] = '查询成功!'
            self.query_result['fg'] = 'green'
            self.query_show()
        except:
            self.query_result.configure(text='名称输入错误!', fg='red')

    def button_draw(self):
        """按键返回信息设置"""
        # 首先:输入的信息获取
        self.source_node = self.source.get()
        self.target_node = self.target.get()
        # 然后:查询结果显示
        try:
            self.query_result['text'] = '图形展示成功!'
            self.query_result['fg'] = 'green'
            self.draw_show()
        except:
            self.query_result.configure(text='无相关关系,无法展示图形!', fg='red')

    def button_clearn(self):
        """清空输入框内容"""
        self.source_node = self.source.get()
        self.target_node = self.target.get()
        self.len_source = len(self.source_node)
        self.len_target = len(self.target_node)
        self.source.delete(0, self.len_source)
        self.target.delete(0, self.len_target)
        self.query_result.configure(text='清理成功!', fg='green')

    def query_show(self):
        """查询结果展示"""
        # 关联关系判断
        print_information = get_relation(graph, self.source_node, self.target_node)
        # 文字信息
        tm.showinfo("查询结果显示", print_information)

    def draw_show(self):
        """图形结果展示"""
        # 关系图绘制
        draw_graph_sub(graph, self.source_node, self.target_node)

五.客户关联关系判断

数据获取并构建图

MultiDiGraph 构建多重有向图
add_edges_from 添加节点及边信息
全图不适合对一线作业人员展示,此处不做绘图操作

def get_data():
    """
        获取数据并生成图
    """
    # 数据获取
    file_list = os.listdir()
    file_path = [x for x in file_list if x.find('.xlsx') != -1][0]
    data_str = pd.read_excel(file_path)

    # Dataframe数据生成字典格式
    # 情况一:数据为明细数据
    data_list = data_str.iloc[:, 0:2].values.tolist()
    data_list = [tuple(x) for x in data_list]
    relation_dict = Counter(data_list)

    # 情况二:数据为统计数据
    # data_keys = data_str.iloc[:, 0:2].values.tolist()
    # data_values = data_str.iloc[:, -1].values.tolist()
    # relation_dict = {tuple(k): v for k, v in zip(data_keys, data_values)}

    relation_data = {k: v for k, v in relation_dict.items() if v > 0}
    relation_data = dict(sorted(relation_data.items(), key=lambda x: x[1], reverse=True))
    # 图形建立
    gh = nx.MultiDiGraph()
    gh.add_edges_from(relation_data)

    return gh, relation_data

关系判断并返回结果信息

has_path 判断是否有关联关系
shortest_path_length 获取最短路径长度

def get_relation(g, source, target):
    """
        根据起始和终止节点返回信息
    """
    # 判断是否有关联
    has_path = '是' if nx.has_path(g, source, target) is True else '否'
    has_path_re = '是' if nx.has_path(g, target, source) is True else '否'
    if has_path == '是':
        # 最短路径长度
        shortest_path_length = nx.shortest_path_length(g, source, target) - 1
        # 最短路径
        shortest_path_length = '直连' if shortest_path_length == 0 else str(shortest_path_length)
        print_information = '结果打印'.center(30, '=') \
                            + '\n' + '{}和{}是否有关联:'.format(source, target) + has_path \
                            + '\n' + '{}和{}关联经过几个人:'.format(source, target) + shortest_path_length \
                            + '\n' + '{}和{}是否双向关联:'.format(source, target) + has_path_re \
                            + '\n' + '打印完毕'.center(30, '=')
    else:
        print_information = '结果打印'.center(30, '=') \
                            + '\n' + '{}和{}是否有关联:'.format(source, target) + has_path \
                            + '\n' + '{}和{}是否反向关联:'.format(source, target) + has_path_re \
                            + '\n' + '打印完毕'.center(30, '=')

    return print_information

六.客户关联图形绘制

此部分的核心在于替换敏感信息的操作,图形颜色设置
画图操作用matplotlib ,可自由选择保存

def draw_graph_sub(g, source, target):
    """返回最短路径图"""
    shortest_paths = list(nx.all_shortest_paths(g, source, target))
    try:
        shortest_paths_re = list(nx.all_shortest_paths(g, target, source))
        shortest_paths = shortest_paths + shortest_paths_re
    except nx.NetworkXNoPath:
        pass
    finally:
        gs = nx.DiGraph()
        # 数据集合
        node_set = []
        [node_set.extend(paths) for paths in shortest_paths]
        node_unique = set(node_set)
        # 生成字典
        node_encode = []
        i = 101
        for nu in node_unique:
            if nu == source or nu == target:
                node_encode.append(nu)
            else:
                node_encode.append(str(i))
                i += 1
        node_encode_dict = {k: v for k, v in zip(node_unique, node_encode)}

        # 添加节点和边信息
        for paths in shortest_paths:
            # 替换敏感信息
            paths_encode = []
            for pts in paths:
                for k, v in node_encode_dict.items():
                    if pts == k:
                        node_rp = pts.replace(k, v)
                        paths_encode.append(node_rp)

            paths_pro = [(x, y) for x, y in zip(paths_encode[:-1], paths_encode[1:])]
            for edge in paths_pro:
                gs.add_edge(edge[0], edge[1])

        # 子图绘制
        plt.figure(figsize=(5, 3))
        nx.draw(gs,
                with_labels=True,
                node_color='#FFA500',
                edge_color='#BC8F8F',
                )
        plt.rcParams['font.sans-serif'] = ['SimHei']
        plt.title('{}和{}关联关系图:'.format(source, target))
        plt.show()

七.代码的简单封装

Anaconda Prompt 下安装 pyinstaller 库,安装成功后可使用下方代码进行封装。
后续会专门写一个文章,解决封装后代码文件大、图标丑的问题

Pyinstaller -F -w relation.py

上述简单封装后文件较大,优化封装具体见本人另一文章windows下python的pip安装和虚拟环境使用

八.总结

整体操作过程为 数据处理——图形构建——关键信息提取——子图绘制展示——图形化界面设置——代码封装。