气泡图、桑基图的绘制

1、气泡图

使用气泡图分析某一年中国同欧洲各国之间的贸易情况。
气泡图分析的三个维度:
• 进口额:横轴
• 出口额:纵轴
• 进出口总额:气泡大小

数据来源:链接: 国家统计局数据

数据概览(进出口总额,进口总额和出口总额数据格式与总额类似)
在这里插入图片描述

(1)数据预处理

1. 知识点:

  1. 通过 .str 来调用字符串处理方法
  2. extract 方法的作用是根据指定的正则表达式模式,从字符串中提取出符合模式的部分。它会返回一个新的 DataFrame 或者 Series
  3. 正则表达式 r’同(.*?)进出口总额’:整体意思是捕获 “同” 后面直到遇到 “进出口总额” 之前的任意字符内容。

2. 代码

#数据预处理:填充缺失值、提取国家名称
# 提取国家名称替换 country 列
df['country'] = df['country'].str.extract(r'同(.*?)进出口总额')
# 将 input 列中的缺失值用 0 填充
df['input'] = df['input'].fillna(0)
# 将结果保存为 Excel 文件
df.to_excel('/初始数据_2023_预处理.xlsx', index=False)

3. 结果:

备注:删除 “欧洲” 这样一行数据,避免造成数据量级差别较大造成的不美观
在这里插入图片描述

(2)可视化

1. 知识点:

  1. plt.cm.tab20:cm 是 matplotlib 中颜色映射(colormap)模块。tab20 是 matplotlib 内置的一种颜色映射表,它包含 20 种不同的颜色 ,这些颜色在视觉上有较好的区分度,适用于区分多个类别。
  2. linspace 函数用于在指定的区间内生成均匀间隔的数值序列。
  3. 使用plotly.express绘制气泡图并添加悬停提示

2. 代码:

import pandas as pd
import plotly.express as px
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import numpy as np# 读取Excel文件数据
df = pd.read_excel('初始数据_2023_预处理.xlsx', engine='openpyxl')# 使用seaborn设置风格
sns.set_style("whitegrid")# 自定义颜色映射
colors = plt.cm.tab20(np.linspace(0, 5, len(df)))
cmap = ListedColormap(colors)# 使用plotly.express绘制气泡图并添加悬停提示
fig = px.scatter(df, x='input', y='output', size='total',color='country',color_discrete_sequence=cmap.colors,labels={'input': '进口额(万美元)', 'output': '出口额(万美元)', 'total': '进出口总额(万美元)','country': '国家/地区'},title='中国与部分欧洲国家进出口气泡图')
# 更新标记点为圆形
fig.update_traces(marker=dict(symbol='circle'))# 添加进出口平衡辅助线
fig.add_shape(type="line",x0=df['input'].min(),  # 辅助线起点x坐标为进口额最小值y0=df['input'].min(),  # 辅助线起点y坐标(与进口额相等,保证在平衡线上 )x1=df['input'].max(),  # 辅助线终点x坐标为进口额最大值y1=df['input'].max(),  # 辅助线终点y坐标(与进口额相等,保证在平衡线上 )line=dict(color="red",  # 辅助线颜色设为红色width=2,dash="dash"  # 辅助线样式设为虚线)
)# 为顺差区域添加注释
fig.add_annotation(xref="x",yref="y",x=df['input'].max() * 0.7,  # 注释x坐标位置y=df['input'].max() * 1.3,  # 注释y坐标位置text="顺差",  # 注释文本font=dict(size=12,color="green"  # 注释文字颜色),showarrow=False  # 不显示箭头
)# 为逆差区域添加注释
fig.add_annotation(xref="x",yref="y",x=df['input'].max() * 0.7,y=df['input'].max() * 0.4,text="逆差",font=dict(size=12,color="orange"),showarrow=False
)# 显示图形
fig.show()

3. 结果:

在这里插入图片描述

2、动态气泡图:

(1)数据预处理

1. 知识点:

  1. melt 函数用于将数据从宽格式转换为长格式
    • id_vars=[‘指标’] :指定在重塑过程中保持不变的列,这里 ‘指标’ 列的内容会被保留。
    • var_name=‘年份’ :将原来宽格式数据中的列名(除 id_vars 列外)转换为长格式中的一列,并将该列命名为 ‘年份’ 。
    • value_name=‘进出口总额’ :将原来宽格式数据中对应的值转换为长格式中的一列,并将该列命名为 ‘进出口总额’ 。通过这一步,数据的结构变得更便于后续分析。
  2. 提取年份中的数字并转换类型
    • df_total[‘年份’].str.extract(‘(\d+)’) :使用 str.extract 方法,结合正则表达式 (\d+) 从 ‘年份’ 列的字符串中提取连续的数字部分。(\d+) 表示捕获一个或多个数字。
    • .astype(int) :将提取出的数字字符串转换为整数类型,这样 ‘年份’ 列的数据类型就变为整数,方便后续进行数值相关的操作或分析。
  3. dropna 函数用于删除包含缺失值的行。subset=[‘国家’] 表示只检查 ‘国家’ 这一列,如果这一列存在缺失值(NaN ),则删除对应的行。这样可以保证数据集中的 ‘国家’ 列没有缺失值,使后续基于该列的分析更加可靠。

2. 代码:

import pandas as pdpd.set_option('display.max_columns', None)
pd.set_option('display.expand_frame_repr', False)
# 读取数据
def read_data():# 读取出口数据df_export = pd.read_csv('年度数据(1).csv', encoding='utf-8')df_export = df_export.melt(id_vars=['指标'], var_name='年份', value_name='出口额')df_export['年份'] = df_export['年份'].str.extract('(\d+)').astype(int)df_export['国家'] = df_export['指标'].str.extract('中国向(.*?)出口总额')df_export = df_export.dropna(subset=['国家'])# 读取进口数据df_import = pd.read_csv('年度数据(2).csv', encoding='utf-8')df_import = df_import.melt(id_vars=['指标'], var_name='年份', value_name='进口额')df_import['年份'] = df_import['年份'].str.extract('(\d+)').astype(int)df_import['国家'] = df_import['指标'].str.extract('中国从(.*?)进口总额')df_import = df_import.dropna(subset=['国家'])# 读取进出口总额数据df_total = pd.read_csv('年度数据.csv', encoding='utf-8')df_total = df_total.melt(id_vars=['指标'], var_name='年份', value_name='进出口总额')df_total['年份'] = df_total['年份'].str.extract('(\d+)').astype(int)df_total['国家'] = df_total['指标'].str.extract('中国同(.*?)进出口总额')df_total = df_total.dropna(subset=['国家'])# 合并数据df = pd.merge(df_export, df_import, on=['国家', '年份'])df = pd.merge(df, df_total, on=['国家', '年份'])# 计算贸易差额df['贸易差额'] = df['出口额'] - df['进口额']df['顺差/逆差'] = df['贸易差额'].apply(lambda x: '顺差' if x > 0 else '逆差')return df# 读取数据
df = read_data()# 将 DataFrame 存储为 CSV 文件
df.to_csv('中国进出口贸易数据.csv', index=False, encoding='utf-8-sig')  # utf-8-sig 支持 Excel 中文显示
print("\n数据已保存到 '中国进出口贸易数据.csv'")

3. 结果:

在这里插入图片描述

(2)可视化V1 —— 展示贸易顺差/逆差随着年份的变化

1. 实现步骤

a. 数据分组:

函数 create_bubble_chart 接受一个 DataFrame 对象 df 作为输入。
使用 groupby 方法按 ‘年份’ 和 ‘国家’ 对数据进行分组,然后使用 agg 方法对分组后的数据进行聚合操作:
‘出口额’、‘进口额’、‘进出口总额’ 和 ‘贸易差额’ 列使用 ‘sum’ 方法进行求和。
‘顺差/逆差’ 列使用 ‘first’ 方法,即取每组中的第一个值(假设每组中该值是相同的)。
最后使用 reset_index 方法重置索引,使分组的 ‘年份’ 和 ‘国家’ 变为普通列。

df_grouped = df.groupby(['年份', '国家']).agg({'出口额': 'sum','进口额': 'sum','进出口总额': 'sum','贸易差额': 'sum','顺差/逆差': 'first'}).reset_index()
b. 创建气泡图

使用 plotly.express 库的 scatter 函数创建一个散点图(气泡图):
x 和 y 分别指定为 ‘进口额’ 和 ‘出口额’ 列。
size 指定为 ‘进出口总额’ 列,用于表示气泡的大小。
color 指定为 ‘顺差/逆差’ 列,用于根据贸易状况给气泡上色。
hover_name 指定为 ‘国家’ 列,当鼠标悬停在气泡上时显示国家名称。
animation_frame 指定为 ‘年份’ 列,使图表按年份进行动态变化。
animation_group 指定为 ‘国家’ 列,确保每个国家的数据在动画中保持一致。
size_max 设置气泡的最大大小为 60。
range_x 和 range_y 设置 x 轴和 y 轴的范围,分别为进口额和出口额最大值的 1.1 倍。
labels 字典用于自定义图表中各轴和图例的标签。
title 设置图表的标题。
color_discrete_map 字典指定了 ‘顺差’ 和 ‘逆差’ 对应的颜色。

    fig = px.scatter(df_grouped,x="进口额",y="出口额",size="进出口总额",color="顺差/逆差",hover_name="国家",animation_frame="年份",animation_group="国家",size_max=60,range_x=[0, df_grouped['进口额'].max() * 1.1],range_y=[0, df_grouped['出口额'].max() * 1.1],labels={"进口额": "进口额 (万美元)","出口额": "出口额 (万美元)","进出口总额": "进出口总额","顺差/逆差": "贸易状况"},title="中国与欧洲各国贸易情况 (2014-2023)",color_discrete_map={"顺差": "blue","逆差": "red"})
c. 添加辅助线

for frame in fig.frames::这是一个循环,遍历 fig(即创建的气泡图对象)中的每一个 frame(帧)。因为这个气泡图是动态的,按年份作为动画帧展示数据变化,所以这里要对每一个帧都添加辅助线,以保证在动画的每一帧中都能显示平衡线。
frame.data += (…):frame.data 表示每一帧中的数据集合,这里使用 += 操作符向每一帧的数据集合中添加一个新的 go.Scatter 对象。
go.Scatter 对象用于创建一个散点图(在这里用于创建一条线):
x=[0, max_value] 和 y=[0, max_value]:指定了这条线的起点 (0, 0) 和终点 (max_value, max_value),这样就形成了 y = x 的直线。
mode=‘lines’:表示这个 go.Scatter 对象的模式是绘制线。
line=dict(color=‘gray’, dash=‘dash’):设置线的属性,颜色为灰色,样式为虚线。
name=‘平衡线 (出口=进口)’:给这条线命名为 ‘平衡线 (出口=进口)’,用于标识这条线的含义。
showlegend=False:设置这条线不显示在图例中,因为这条辅助线主要是为了视觉上的参考,不需要在图例中占据空间。

# 获取最大值的110%用于辅助线 这样可以确保所有的数据点都在辅助线所界定的区域内显示,使图表更加完整和美观
max_value = max(df_grouped['进口额'].max(), df_grouped['出口额'].max()) * 1.1# 添加辅助线 (y = x)
for frame in fig.frames:frame.data += (go.Scatter(x=[0, max_value],y=[0, max_value],mode='lines',line=dict(color='gray', dash='dash'),name='平衡线 (出口=进口)',showlegend=False
d. 更新布局
  1. hovermode 用于设置当鼠标悬停在图形上时的交互模式。这里设置为 “closest”,表示当鼠标悬停在图表上时,会显示离鼠标位置最近的数据点的详细信息(例如国家名称、进口额、出口额等,这些信息是在创建气泡图时通过 hover_name 等参数设置的)。
  2. updatemenus 用于在图形中添加一些交互按钮或菜单。这里创建了一个类型为 “buttons” 的 updatemenus,即添加按钮。
    buttons 是一个列表,用于定义按钮的具体属性。这里列表中只有一个按钮,通过 dict 来设置按钮的属性。
    label=“播放” 设置按钮的显示文本为 “播放”。
    method=“animate” 表示当点击这个按钮时,执行的操作是启动动画。
    args 是传递给 animate 方法的参数。[None, {“frame”: {“duration”: 1000, “redraw”: True}, “fromcurrent”: True}] 中,None 表示不指定特定的帧序列来播放动画;{“frame”: {“duration”: 1000, “redraw”: True}, “fromcurrent”: True} 表示设置动画帧的持续时间为 1000 毫秒,并且在播放动画时重新绘制图形(redraw": True),同时从当前帧开始播放动画(“fromcurrent”: True)。
	fig.update_layout(xaxis_title="进口额 (万美元)",yaxis_title="出口额 (万美元)",legend_title="贸易状况",hovermode="closest",transition={'duration': 1000},updatemenus=[dict(type="buttons",buttons=[dict(label="播放",method="animate",args=[None, {"frame": {"duration": 1000, "redraw": True}, "fromcurrent": True}]),dict(label="暂停",method="animate",args=[[None], {"frame": {"duration": 0, "redraw": False}, "mode": "immediate","transition": {"duration": 0}}])])])
e. 添加初始辅助线

fig.add_trace(…):
fig 是 plotly 里的图形对象,代表了整个图表。add_trace 方法的作用是往图表里添加一个新的绘图轨迹(trace)。绘图轨迹可以理解成图表中的一个独立绘图元素,例如散点图、折线图等。这里添加的是一条直线。

    fig.add_trace(go.Scatter(x=[0, max_value],y=[0, max_value],mode='lines',line=dict(color='gray', dash='dash'),name='平衡线 (出口=进口)'))

3. 结果

图片:

在这里插入图片描述

视频:

动态气泡图_顺逆差

(3)可视化V1

1. 改进

体现具体国家而不仅仅是顺逆差

2. 结果

动态气泡图_彩色

3、桑基图 Sankey diagram:分析与可视化不同因素之间的关系,借助绘制桑基图来呈现这些因素之间的关联和流动情况

(1)桑基图简介

  • 组成要素
  1. 节点:代表不同的类别或状态,通常用矩形或其他形状表示。例如,在能源流动的桑基图中,节点可以是不同的能源来源(如煤炭、石油、天然气)和能源使用部门(如工业、交通、居民生活)。
  2. 边或流线:连接节点的线条,用于表示数据的流动方向和数量。边的宽度与所代表的数据量成正比,因此可以直观地看出不同路径上数据的相对大小。
  • 特点
  1. 可视化数据流动:能够清晰地展示数据从一个状态或类别到另一个状态或类别的流动过程,使复杂的流程和关系变得直观易懂。
  2. 定量展示:通过边的宽度准确地展示数据的数量,让观众可以直观地比较不同部分的数据大小,了解各部分在整体中所占的比例。
  3. 整体守恒:桑基图中所有流入节点的流量总和等于所有流出节点的流量总和,体现了数据在整个系统中的守恒关系,有助于分析数据在各个环节的分配和转化情况。

(2)数据集

  • credit_record.csv
    在这里插入图片描述
  • application_record.csv:
    在这里插入图片描述
  • 数据说明:
    在这里插入图片描述

(3)实现步骤

1.导入包、读取数据

import pandas as pd
import plotly.graph_objects as go# 读取数据
app = pd.read_csv("application_record.csv")
credit = pd.read_csv("credit_record.csv")

2.定义函数用于信用状态分类——优先考虑逾期情况最严重的状态

  • 如果列表中有多种状态,首先使用 any(s in [‘1’, ‘2’, ‘3’, ‘4’, ‘5’] for s in status_list) 判断列表中是否存在逾期状态(‘1’ 到 ‘5’)。any() 函数用于判断可迭代对象中是否有任何一个元素满足条件。
  • 如果存在逾期状态,优先考虑逾期最严重的情况,按照逾期天数从多到少的顺序进行判断,返回对应的信用状态分类描述
  • 如果列表中不存在逾期状态,使用 all(s in [‘C’, ‘0’] for s in status_list) 判断列表中的所有状态是否都为 ‘C’(本月已还清)或 ‘0’(逾期 1 - 29 天)。
  • all() 函数用于判断可迭代对象中的所有元素是否都满足条件。 如果是,则返回 ‘Paid off’ 表示已还清。
  • credit.groupby(‘ID’) 按照用户 ID 对信用记录数据 credit 进行分组。
    .apply(classify_credit_status) 对每个分组应用 classify_credit_status 函数,得到每个用户的信用状态分类结果。
    .reset_index() 重置索引,将结果转换为一个数据框。
    credit_status.columns = [‘ID’, ‘CREDIT_STATUS’] 为数据框的列设置名称,分别为 ‘ID’ 和 ‘CREDIT_STATUS’。
f classify_credit_status(group):status_list = group['STATUS'].tolist()# 如果列表中只有一种状态,直接返回该状态对应的分类if len(set(status_list)) == 1:status = status_list[0]if status == '0':return '1-29 days due'elif status == '1':return '30-59 days due'elif status == '2':return '60-89 days due'elif status == '3':return '90-119 days due'elif status == '4':return '120-149 days due'elif status == '5':return 'Over 150 days due or bad debt'elif status == 'C':return 'Paid off this month'elif status == 'X':return 'No loan this month'# 如果列表中有多种状态,需要根据规则判断else:if any(s in ['1', '2', '3', '4', '5'] for s in status_list):# 优先考虑逾期严重的情况if '5' in status_list:return 'Over 150 days due or bad debt'elif '4' in status_list:return '120-149 days due'elif '3' in status_list:return '90-119 days due'elif '2' in status_list:return '60-89 days due'elif '1' in status_list:return '30-59 days due'elif all(s in ['C', '0'] for s in status_list):return 'Paid off'elif 'X' in status_list:return 'No loan this month'else:return 'Complex status'credit_status = credit.groupby('ID').apply(classify_credit_status).reset_index()
credit_status.columns = ['ID', 'CREDIT_STATUS']

3.合并数据

merged = pd.merge(app, credit_status, on='ID')

4.创建字段

依据 FLAG_OWN_REALTY 列创建新列 OWN_REALTY,将 Y 映射为 Yes Property,N 映射为 No Property。

merged['OWN_REALTY'] = merged['FLAG_OWN_REALTY'].map({'Y': 'Yes Property', 'N': 'No Property'})

5.选取需要的字段

df = merged[['NAME_HOUSING_TYPE', 'NAME_INCOME_TYPE', 'OWN_REALTY', 'CREDIT_STATUS']]

6.汇总数据

按照 NAME_HOUSING_TYPE、NAME_INCOME_TYPE、OWN_REALTY 和 CREDIT_STATUS 进行分组,统计每组的数量,将结果保存到 count 列。

df_grouped = df.groupby(['NAME_HOUSING_TYPE', 'NAME_INCOME_TYPE', 'OWN_REALTY', 'CREDIT_STATUS']).size().reset_index(name='count')

7.设置标签和映射

labels:将所有可能的标签(房屋类型、收入类型、是否拥有房产、信用状态)合并,去除重复项后转换为列表。
label_index:创建一个字典,将每个标签映射到一个唯一的索引。

labels = pd.concat([df_grouped['NAME_HOUSING_TYPE'], df_grouped['NAME_INCOME_TYPE'],df_grouped['OWN_REALTY'], df_grouped['CREDIT_STATUS']]).unique().tolist()
label_index = {label: i for i, label in enumerate(labels)}

8.构建链接

source、target 和 value:分别代表桑基图中链接的起始节点索引、结束节点索引和链接的值(即每组的数量)。
通过三次循环构建三组链接:房屋类型到收入类型、收入类型到是否拥有房产、是否拥有房产到信用状态。

source = []
target = []
value = []# Housing ➝ Income Type
for _, row in df_grouped.iterrows():source.append(label_index[row['NAME_HOUSING_TYPE']])target.append(label_index[row['NAME_INCOME_TYPE']])value.append(row['count'])# Income Type ➝ Own Realty
for _, row in df_grouped.iterrows():source.append(label_index[row['NAME_INCOME_TYPE']])target.append(label_index[row['OWN_REALTY']])value.append(row['count'])# Own Realty ➝ Credit Status
for _, row in df_grouped.iterrows():source.append(label_index[row['OWN_REALTY']])target.append(label_index[row['CREDIT_STATUS']])value.append(row['count'])

9.绘制桑基图

go.Sankey:创建一个桑基图对象。
node:设置节点的属性,如节点间距、厚度、线条颜色和宽度、标签等。
link:设置链接的属性,如起始节点索引、结束节点索引和链接的值。
fig.update_layout:更新图表的布局,设置标题和字体大小。
fig.show():显示桑基图。

fig = go.Figure(data=[go.Sankey(node=dict(pad=15,thickness=20,line=dict(color="black", width=0.5),label=labels),link=dict(source=source,target=target,value=value))])fig.update_layout(title_text="House Type ➝ Income Type ➝ Owns Property ➝ Credit Status", font_size=12)
fig.show()

(4)结果

  • House Type → Income Type → Owns Property → Credit Status
    在这里插入图片描述

4、桑基图2

同理根据以下代码绘制结果如图

import pandas as pd
import plotly.graph_objects as go# 读取数据
app = pd.read_csv("application_record.csv")
credit = pd.read_csv("credit_record.csv")# 一、处理收入分组
def map_income_level(income):if income < 50000:return "Very Low income"elif income < 100000:return "Low income"elif income < 150000:return "Lower - middle income"elif income < 200000:return "Middle income"elif income < 300000:return "Upper - middle income"elif income < 400000:return "Moderate - high income"elif income < 600000:return "High income"else:return "Very High income"app["INCOME_LEVEL"] = app["AMT_INCOME_TOTAL"].apply(map_income_level)# 二、处理信用记录中的月收支平衡时间段
def map_month_balance(group):earliest = group['MONTHS_BALANCE'].min()if earliest >= -6:return "Within 6 months"elif earliest >= -12:return "6 - 12 months ago"elif earliest >= -24:return "1 - 2 years ago"elif earliest >= -36:return "2 - 3 years ago"elif earliest >= -48:return "3 - 4 years ago"elif earliest >= -60:return "4 - 5 years ago"else:return "Over 5 years ago"credit_time = credit.groupby('ID').apply(map_month_balance).reset_index()
credit_time.columns = ['ID', 'MONTH_BALANCE_GROUP']# 三、合并数据
merged = pd.merge(app, credit_time, on='ID')# 四、选取需要的字段
df = merged[['CODE_GENDER', 'INCOME_LEVEL', 'NAME_EDUCATION_TYPE', 'NAME_FAMILY_STATUS', 'MONTH_BALANCE_GROUP']]# 五、分组统计
df_grouped = df.groupby(['CODE_GENDER', 'INCOME_LEVEL', 'NAME_EDUCATION_TYPE', 'NAME_FAMILY_STATUS', 'MONTH_BALANCE_GROUP']).size().reset_index(name='count')# 六、构建节点标签及索引映射
labels = pd.concat([df_grouped['CODE_GENDER'],df_grouped['INCOME_LEVEL'],df_grouped['NAME_EDUCATION_TYPE'],df_grouped['NAME_FAMILY_STATUS'],df_grouped['MONTH_BALANCE_GROUP']
]).unique().tolist()label_index = {label: i for i, label in enumerate(labels)}# 七、构建桑基图的 source、target、value
source, target, value = [], [], []# Gender ➝ Income Level
for _, row in df_grouped.iterrows():source.append(label_index[row['CODE_GENDER']])target.append(label_index[row['INCOME_LEVEL']])value.append(row['count'])# Income Level ➝ Education Level
for _, row in df_grouped.iterrows():source.append(label_index[row['INCOME_LEVEL']])target.append(label_index[row['NAME_EDUCATION_TYPE']])value.append(row['count'])# Education Level ➝ Marital Status
for _, row in df_grouped.iterrows():source.append(label_index[row['NAME_EDUCATION_TYPE']])target.append(label_index[row['NAME_FAMILY_STATUS']])value.append(row['count'])# Marital Status ➝ Month Balance
for _, row in df_grouped.iterrows():source.append(label_index[row['NAME_FAMILY_STATUS']])target.append(label_index[row['MONTH_BALANCE_GROUP']])value.append(row['count'])# 八、绘制桑基图
fig = go.Figure(data=[go.Sankey(node=dict(pad=15,thickness=20,line=dict(color="black", width=0.5),label=labels),link=dict(source=source,target=target,value=value))])fig.update_layout(title_text="Gender / Income Level / Education Level / Marital Status / Month Balance",font_size=12,height=700
)
fig.show()

在这里插入图片描述

5、存在的问题——信用状态的判断

  • 最近一个月的信用状态?
    在这里插入图片描述

  • 逾期最严重的信用状态?

# 图2 House Type → Income Type →  Owns Property →  Credit Statusimport pandas as pd
import plotly.graph_objects as go# 读取数据
app = pd.read_csv("application_record.csv")
credit = pd.read_csv("credit_record.csv")# 信用状态分类
def classify_credit_status(group):status_list = group['STATUS'].tolist()# 如果列表中只有一种状态,直接返回该状态对应的分类if len(set(status_list)) == 1:status = status_list[0]if status == '0':return '1-29 days due'elif status == '1':return '30-59 days due'elif status == '2':return '60-89 days due'elif status == '3':return '90-119 days due'elif status == '4':return '120-149 days due'elif status == '5':return 'Over 150 days due or bad debt'elif status == 'C':return 'Paid off this month'elif status == 'X':return 'No loan this month'# 如果列表中有多种状态,需要根据规则判断else:if any(s in ['1', '2', '3', '4', '5'] for s in status_list):# 优先考虑逾期严重的情况if '5' in status_list:return 'Over 150 days due or bad debt'elif '4' in status_list:return '120-149 days due'elif '3' in status_list:return '90-119 days due'elif '2' in status_list:return '60-89 days due'elif '1' in status_list:return '30-59 days due'elif all(s in ['C', '0'] for s in status_list):return 'Paid off'elif 'X' in status_list:return 'No loan this month'else:return 'Complex status'credit_status = credit.groupby('ID').apply(classify_credit_status).reset_index()
credit_status.columns = ['ID', 'CREDIT_STATUS']# 合并
merged = pd.merge(app, credit_status, on='ID')# 创建字段
merged['OWN_REALTY'] = merged['FLAG_OWN_REALTY'].map({'Y': 'Yes Property', 'N': 'No Property'})df = merged[['NAME_HOUSING_TYPE', 'NAME_INCOME_TYPE', 'OWN_REALTY', 'CREDIT_STATUS']]# 汇总
df_grouped = df.groupby(['NAME_HOUSING_TYPE', 'NAME_INCOME_TYPE', 'OWN_REALTY', 'CREDIT_STATUS']).size().reset_index(name='count')# 标签和映射
labels = pd.concat([df_grouped['NAME_HOUSING_TYPE'], df_grouped['NAME_INCOME_TYPE'],df_grouped['OWN_REALTY'], df_grouped['CREDIT_STATUS']]).unique().tolist()
label_index = {label: i for i, label in enumerate(labels)}# 构建链接
source = []
target = []
value = []# Housing ➝ Income Type
for _, row in df_grouped.iterrows():source.append(label_index[row['NAME_HOUSING_TYPE']])target.append(label_index[row['NAME_INCOME_TYPE']])value.append(row['count'])# Income Type ➝ Own Realty
for _, row in df_grouped.iterrows():source.append(label_index[row['NAME_INCOME_TYPE']])target.append(label_index[row['OWN_REALTY']])value.append(row['count'])# Own Realty ➝ Credit Status
for _, row in df_grouped.iterrows():source.append(label_index[row['OWN_REALTY']])target.append(label_index[row['CREDIT_STATUS']])value.append(row['count'])# 绘制桑基图
fig = go.Figure(data=[go.Sankey(node=dict(pad=15,thickness=20,line=dict(color="black", width=0.5),label=labels),link=dict(source=source,target=target,value=value))])fig.update_layout(title_text="House Type ➝ Income Type ➝ Owns Property ➝ Credit Status", font_size=12)
fig.show()
  • 所有的信用状态全部考虑的数据流向和流量是否准确(因为会把重复的用户计入)
import pandas as pd
import plotly.graph_objects as go# 读取数据
app = pd.read_csv("application_record.csv")
credit = pd.read_csv("credit_record.csv")# 按 ID 合并数据集
merged = pd.merge(app, credit, on='ID')# 创建新字段 OWN_REALTY
merged['OWN_REALTY'] = merged['FLAG_OWN_REALTY'].map({'Y': 'Yes Property', 'N': 'No Property'})# 定义状态映射字典
status_mapping = {'0': '1-29 days due','1': '30-59 days due','2': '60-89 days due','3': '90-119 days due','4': '120-149 days due','5': 'Over 150 days due or bad debt','C': 'Paid off this month','X': 'No loan this month'
}# 将 STATUS 列映射为描述性状态
merged['CREDIT_STATUS'] = merged['STATUS'].map(status_mapping)# 选择用于绘制桑基图的列
columns = ['NAME_HOUSING_TYPE', 'NAME_INCOME_TYPE', 'OWN_REALTY', 'CREDIT_STATUS']# 分组统计每个组合的数量
df_grouped = merged.groupby(columns).size().reset_index(name='count')# 生成所有唯一的标签
labels = pd.concat([df_grouped[col] for col in columns]).unique().tolist()
# 为每个标签分配一个索引
label_index = {label: i for i, label in enumerate(labels)}# 初始化存储链接信息的列表
source = []
target = []
value = []# 构建所有可能的链接
for i in range(len(columns) - 1):for _, row in df_grouped.iterrows():source.append(label_index[row[columns[i]]])target.append(label_index[row[columns[i + 1]]])value.append(row['count'])# 创建桑基图对象
fig = go.Figure(data=[go.Sankey(node=dict(pad=15,thickness=20,line=dict(color="black", width=0.5),label=labels),link=dict(source=source,target=target,value=value))])# 更新图表布局,设置标题和字体大小
fig.update_layout(title_text="House Type ➝ Income Type ➝ Owns Property ➝ Credit Status", font_size=12)
# 显示桑基图
fig.show()

在这里插入图片描述

6、相关资料:

链接1: 桑基图
链接2: plotly 基本操作手册

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/79381.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端面经-VUE3篇(三)--vue Router(二)导航守卫、路由元信息、路由懒加载、动态路由

一、导航守卫 vue Router 中的 导航守卫&#xff08;Navigation Guards&#xff09; 是一个非常重要的功能&#xff0c;用于在路由切换过程中&#xff0c;拦截、控制、检查或延迟页面跳转。 你可以理解为&#xff1a; &#x1f510; “进门前的保安”&#xff0c;控制哪些页面…

MATLAB实现二氧化硅和硅光纤的单模光波特性与仿真

一.二氧化硅和硅光纤的单模光波特性 利用麦克斯方程的精确解研究二氧化硅和硅亚波长直径导线的单模光波特性。研究了单模条件、模场。 二氧化硅光纤导线是圆形截面&#xff0c;包层是空气包层&#xff0c;阶梯型变化的折射率&#xff0c;导线线径D非常小长度足够长&#xff0…

【Linux系统】第二节—基础指令(2)

hello ~ 好久不见 自己想要的快乐要自己好好争取&#xff01; 云边有个稻草人-个人主页 Linux—本篇文章所属专栏—欢迎订阅—持续更新中 目录 本节课核心指令知识点总结 本节基本指令详解 07.man 指令 08.cp 指令 09.mv 指令 10.cat 指令 11.more 指令 12.less 指令 …

为了结合后端而学习前端的学习日志——【黑洞光标特效】

前端设计专栏 今天给大家带来一个超酷的前端特效——黑洞光标&#xff01;让你的鼠标变成一个会吞噬光粒子的迷你黑洞&#xff0c;点击时还会喷射出绿色能量粒子&#xff01;&#x1f320; &#x1f680; 效果预览 想象一下&#xff1a;你的鼠标变成一个旋转的黑洞&#xff0…

[硬件电路-11]:模拟电路常见元器件 - 什么是阻抗、什么是输入阻抗、什么是输出阻抗?阻抗、输入阻抗与输出阻抗的全面解析

1. 阻抗&#xff08;Impedance&#xff09; 定义&#xff1a;阻抗是电路或元件对交流信号&#xff08;AC&#xff09;流动的阻碍能力&#xff0c;用符号Z表示&#xff0c;单位为欧姆&#xff08;Ω&#xff09;。它综合了电阻&#xff08;R&#xff09;、电感&#xff08;L&am…

机器学习和深度学习的对比

深度 数据经过深层网络后&#xff0c;语义信息表征能力强&#xff0c;对几何细节信息表征能力弱。 数据依赖性 深度学习算法需要大量的数据来训练&#xff0c;而传统的机器学习使用制定的规则。所以&#xff0c;当数据量少时&#xff0c;深度学习的性能差于机器学习&#xf…

Kubernetes 安装 minikube

安装 minikube 在 Ubuntu 上安装 minikube minikube 是一个工具&#xff0c;它可以在本地快速运行一个单节点的 Kubernetes 集群。它主要用于&#xff1a;本地学习 Kubernetes、测试和开发 Kubernetes 应用程序、快速尝试 Kubernetes 的功能。 系统配置最低要求如下 CPU&#…

【学习笔记】深度学习:典型应用

作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程&#xff0c;深度学习领域研究生必读教材),开始深度学习领域学习&#xff0c;深入全面的理解深度学习的理论知识。 之前的文章参考下面的链接&#xf…

ComputeShader绘制全屏纯色纹理

参考 Getting Started With Compute Shaders In Unity 环境 Win10 Unity20194.40 全屏纯色纹理示例 使用ComputerShader逐个像素设置颜色 ComputeShader脚本 设置纹理颜色 #pragma kernel CSMainRWTexture2D<float4> Result;//纹理 half4 solidColor;//颜色[numth…

数学实验(Matlab语言环境和线性代数实验)

一、Matlab语言环境和线性代数实验 1.Matlab语言环境 Matlab简介 Matlab&#xff1a;Matrix Laboratry 矩阵实验室 Matlab 提供了强大的科学计算、灵活的程序设计流程、高质量的图形可视化与界面设计等功能&#xff0c;被广泛应用于科学计算、控制系统、信息处理等领域的分…

Android面试总结之GC算法篇

一、GC 机制核心原理与算法 面试题 1&#xff1a;Android 中为什么采用分代回收&#xff1f;分代策略如何优化 GC 效率&#xff1f; 标准答案&#xff1a; 分代回收基于对象生命周期的差异&#xff0c;将堆分为年轻代&#xff08;Young Gen&#xff09;和老年代&#xff08;Ol…

仿腾讯会议——注册登录UI

1、加载素材 2、新添加资源类 3、加载图片 4、添加左侧图片 在左侧添加一个标签 选择图片 选择图片 勾选保证图片不变形 5、修改组件名称 6、设置密码输入框 5、切换 6、编辑提示框 7、定义提交和清空的槽函数 8、设置页面标题和最先显示页面 9、清空登录信息函数实现 10、清空…

Kotlin 常见问题

以下从基础、中级、高级三个难度等级为你提供 Kotlin 面试题及参考答案&#xff1a; 基础难度 1. Kotlin 中 val 和 var 的区别是什么&#xff1f; 答案要点&#xff1a;val 用于声明不可变变量&#xff0c;类似于 Java 中的 final 变量&#xff0c;一旦赋值后就不能再重新赋…

高频数据冲击数据库的技术解析与应对方案

目录 前言一、问题现象与影响分析1.1 典型场景表现1.2 核心问题分类 二、失效根源深度剖析2.1 架构设计缺陷2.2 缓存策略缺陷 三、解决方案与最佳实践3.1 缓存架构设计3.1.1 分层缓存架构3.1.2 热点数据识别 3.2 缓存策略优化3.2.1 动态过期时间算法3.2.2 缓存更新策略对比 3.3…

[Spring] Sentinel详解

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

清除浮动的重要性及解决办法

由于父级盒子很多情况下&#xff0c;不方便给高度&#xff0c;但是子盒子浮动又不占有位置&#xff0c;最后父级盒子高度为0时&#xff0c;就会影响下面的标准流盒子。 一、为什么要清除浮动 父元素高度塌陷&#xff1a; 如果父元素内部的所有子元素都浮动了&#xff0c;并且没…

域名与官网的迷思:数字身份认证的全球困境与实践解方-优雅草卓伊凡

域名与官网的迷思&#xff1a;数字身份认证的全球困境与实践解方-优雅草卓伊凡 一、官网概念的法律与技术界定 1.1 官网的实质定义 当卓伊凡被问及”公司域名就是官网吗”这一问题时&#xff0c;他首先指出&#xff1a;”这相当于问’印着某公司logo的建筑就是该公司总部吗’…

kotlin flatMap 变换函数的特点和使用场景

Kotlin 中的 flatMap 是一个非常常用的函数&#xff0c;尤其在处理集合&#xff08;如 List、Set 等&#xff09;时。它结合了 map 和 flatten 的功能&#xff0c;常用于将多个集合扁平化为一个单一的集合。 一、flatMap 函数的特点 转换 扁平化&#xff1a; 对集合中的每个元…

java学习之数据结构:二、链表

本节介绍链表 目录 1.什么是链表 1.1链表定义 1.2链表分类 2.链表实现 2.1创建链表 1&#xff09;手动创建 2&#xff09;创建链表类进行管理链表的相关操作 2.2添加元素 1&#xff09;头插法 2&#xff09;尾插法 3&#xff09;任意位置插入 2.3删除 2.4查找 1&…

【计算机网络-应用层】解析HTTP会话保持:Cookie与Session的原理与实践

&#x1f4da; 博主的专栏 &#x1f427; Linux | &#x1f5a5;️ C | &#x1f4ca; 数据结构 | &#x1f4a1;C 算法 | &#x1f152; C 语言 | &#x1f310; 计算机网络 上篇文章&#xff1a;实现HTTP服务器 下篇文章&#xff1a;传输层协议-UDP 文章摘要&…