在Python中实现多重共线性的可视化
在Python中实现多重共线性的可视化
1.png网络图的胜利。
简介
什么是多重共线性?
多重共线性是指两个或更多的特征之间相互关联的情况。尽管独立特征和从属特征之间的相关性是需要的,但在某些情况下,独立特征的多重共线性是不太需要的。事实上,它们可以被省略,因为它们并不一定比与之相关的特征更具信息量。因此,识别这些特征是一种特征选择的形式。作为一名数据科学家,在训练预测模型之前,识别和理解数据集中的多重共线性是关键。即使在训练了一个模型之后,限制高度共线性特征也是很重要的,因为它在解释模型时可能会导致误导的结果。
为什么要将多重共线性可视化?
检查独立和从属特征之间的相关性通常是在探索性数据分析期间进行的。它可以提供对特征重要性的早期洞察力,从而很好地理解这些特征对做预测的信息量有多大。对于特征的选择,你不一定要直观地检查特征之间的相关性。你可以使用VIF(变量膨胀因子)等指标来检测多重共线性。然而,将特征之间的相关性可视化,作为提取关于数据集特征的洞察力的一种手段,仍然是值得的。
特征之间的相关性通常使用相关矩阵进行可视化,而相关矩阵则通过热图显示数据集中每个特征的相关系数。不幸的是,如果数据集有大量的特征,那么热图在这一点上可能只是画了一个漂亮的8位艺术作品。由于所产生的热图的尺寸太大,提取任何类型的信息都会非常困难。如果有50个特征,这就是一个形状为50×50的矩阵(Duh²)。颜色和强度可能有助于区分最重要的因素,但也仅此而已。当然,一定有更好的方法。
在这篇博客中,我介绍了三种可视化多重共线性的方法。即,事实上的热图、聚类图和交互式网络图的可视化。我将强调每种可视化的优点和缺点。
注意:使用R(和igraph包),你可以很容易地生成 "相关网络 "图。然而,在Python中,据我所知没有固定的方法。
可视化S&P500的强相关股票。
2.png
S&P500股票变化的树状图。
我将使用S&P500的股票数据(在2020年1月1日和2021年12月31日之间)来可视化拼合的股票。使用yfinance软件包,你可以简单地使用股票代码检索股市数据。在检索股票数据之前,先从维基百科上搜出S&P500股票表,以检索S&P500中的所有当前股票信息。这包括股票的名称,股票代码,相应的部门,以及更多。
虽然我在这篇博客中专门研究了时间序列数据,但所提出的可视化方法是不分数据的。你所需要的只是用Pandas函数.corr()生成的DataFrame的相关矩阵。
import pandas as pd
import seaborn as sns
%matplotlib inline
import numpy as np
import os
import matplotlib.pyplot as plt
import plotly.graph_objects as go
import plotly.express as px
import networkx as nx
from ipywidgets import Layout, widgets
from google.colab import output
output.enable_custom_widget_manager()
import math
import matplotlib.dates as md
!pip install yfinance
import yfinance as yf
payload = pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')
# S&P500 metadata
sp500_table = payload[0]
# mappings
sp500_tickers = sp500_table.Symbol.str.upper().values
sp500_names = sp500_table.Security.values
sp500_sectors = sp500_table["GICS Sector"].values
sp500_sub_sectors = sp500_table["GICS Sub-Industry"].values
sp500_names_mapping = dict(zip(sp500_tickers, sp500_names))
sp500_sector_mapping = dict(zip(sp500_names, sp500_sectors))
sp500_sub_sector_mapping = dict(zip(sp500_names, sp500_sub_sectors))
sector_color_mapping = dict(zip(sp500_sectors, sns.color_palette("pastel", len(sp500_sectors)).as_hex()))
subsector_color_mapping = dict(zip(sp500_sub_sectors, sns.color_palette("pastel", len(sp500_sub_sectors)).as_hex()))
# download S&P500 financial data
tickers = list(sp500_tickers)
prices = yf.download(tickers, start="2020-01-01", end="2021-12-31", interval='1d')
prices = prices["Adj Close"]
prices = prices.rename(columns=sp500_names_mapping)
prices
# impute
for i, row in prices.iterrows():
if row.isnull().mean() > 0.9: prices.drop(i, inplace=True)
prices = prices.loc[:, prices.isnull().mean() < 0.3]
prices = prices.fillna(method='bfill')
print(prices.shape)
# calculate rolling correlation
corr = prices.rolling(60).corr()
corr_ = np.array([corr.loc[i].to_numpy() for i in prices.index if not np.isnan(corr.loc[i].to_numpy()).all()])
corr_ = np.nansum(corr_, axis=0)/len(corr_)
corr_ = pd.DataFrame(columns=prices.columns.tolist(), index=prices.columns.tolist(), data=corr_)
Daily price data of S&P500 stocks..jpeg
Daily price data of S&P500 stocks.
S&P500股票的每日价格数据。
热图
相关性热图可以简单地通过用Pandas生成相关矩阵和用Seaborn绘制矩阵的热图来实现可视化。其结果是一个显示正、负和零相关因素的热图。热图的主要优点是它可以非常容易地用Seaborn生成。颜色有助于区分强相关和弱相关。主要的缺点是它不能随着特征数量的增加而扩大。当特征的数量很大时,它就很难解释。强相关的特征没有被分组,因为顺序是任意的。此外,产生的矩阵是对称的,这意味着显示的一半数值是多余的。矩阵的上/下三角可以通过一些额外的步骤去除。
plt.figure(figsize=(20,20))
sns.heatmap(corr_)
Daily price data of S&P500 stocks..jpeg
显示S&P500股票之间相关因素的热图。
聚类图
乍一看,聚类图与热图基本相似。它同样容易绘制。然而,它对具有强烈相关性的特征进行了聚类,并显示了树状结构以了解不同层次的聚类。与热图不同的是,特征排序的顺序是有作用的。除此以外(还有额外的聚类图),它在视觉上与热图相似。聚类图是一种充分的方式来可视化那些强相关的特征组。也就是说,如果数据集中的特征数量有限。否则,聚类图在检查相邻的特征时,同样会变得混乱难解。
sns.clustermap(corr_, figsize=(20,20))
5.png
聚类图显示分组中的相关股票。
网络图
我提出了两种将多重共线性可视化的方法。那就是使用热图和聚类图。尽管后者是一种改进的方法,可以将强相关的特征群可视化,但它在解释时可能会变得非常令人厌烦,或者在检索洞察力时变得很尴尬,特别是在特征数量很大的情况下。S&P500数据集尤其如此,它包含了500多个特征,如果你考虑更长的时间框架或其他纽约证券交易所,甚至更多。
这些类型的可视化的最大问题是它显示了多余的信息。换句话说,它显示的特征也是弱相关的。我们通常对强关联性感兴趣,而绘制弱关联性只是没有那么有趣。另一个主要的缺点是,视觉空间没有被明智地利用。很难说清一组特征从哪里开始,另一组从哪里结束,最重要的是,各组特征是如何与其他特征相关联的。有很多信息是丢失的或难以区分的。
网络图可以缓解大部分这些问题。下面,它是用networkx软件包生成的。使用networkx和Plotly,我们可以创建一个交互式图。为了限制相邻特征之间的相互联系的数量,只取最大生成树。你可以玩玩滑块,增加最小相关阈值,这显然会导致更少的股票。我还用类似的颜色绘制了具有相同部门的股票。你可以很容易地看到股票是如何根据其行业分组的。
threshold_choice = widgets.FloatSlider(description="Threshold", value=0.8, min=0.5, max=1, step=0.05, continuous_update=False,
orientation='horizontal', layout=Layout(width='500px'), style=dict(description_width= 'initial'))
network = go.FigureWidget(data=[go.Scatter(x=[], y=[], mode='lines', text=[], line=dict(color='MediumPurple', width=10), marker=dict(size=20, line_width=10,line=dict(color='MediumPurple',width=2))),
go.Scatter(x=[], y=[],mode='markers+text', textposition="top center", text=[],hoverinfo='text',textfont_size=12, marker=dict(size=50, color=[],line_width=1))],
layout=go.Layout( showlegend=False, annotations=[], margin=dict(t=40, b=0, l=0, r=0), width=1600, height=800))
df = prices.copy()
correlation_matrix = corr_.to_numpy()
def plot_corr_graph(change):
threshold, corr_mode = None, None
threshold = change.new
tr_ind = np.triu_indices(correlation_matrix.shape[0])
correlation_matrix[tr_ind] = 0
G = nx.from_numpy_matrix(correlation_matrix)
G = nx.relabel_nodes(G, lambda x: df.columns.tolist()[x])
# 49 x 49 - 49 (self corr) / 2 (remove upper triang)
remove = []
for col1, col2, weight in G.edges(data=True):
if math.isnan(weight["weight"]):
remove.append((col1,col2))
if abs(weight["weight"]) < threshold:
remove.append((col1,col2))
G.remove_edges_from(remove)
remove = []
edges = list(sum(G.edges, ()))
for node in G.nodes:
if node not in edges:
remove.append(node)
G.remove_nodes_from(remove)
mst = nx.maximum_spanning_tree(G)
def assign_color(col):
return sector_color_mapping[sp500_sector_mapping[col]]
def assign_color_edge(correlation):
if correlation < 0:
return "#BF0603"
else:
return "#00CC66"
edge_colors = []
node_colors = []
for key, value in nx.get_edge_attributes(mst, 'weight').items():
edge_colors.append(assign_color_edge(value))
for key, value in dict(mst.degree).items():
node_colors.append(assign_color(key))
labels = {n:n for n in mst.nodes()}
node_x = []
node_y = []
tree = nx.fruchterman_reingold_layout(mst, k=0.25).items()
for node, (x_,y_) in tree:
node_x.append(x_)
node_y.append(y_)
def get_dim_of_node(name):
for node, (x,y) in tree:
if node == name:
return x,y
edge_x = []
edge_y = []
weights= []
for node1, node2, w in mst.edges(data=True):
x0, y0 = get_dim_of_node(node1)
x1, y1 = get_dim_of_node(node2)
edge_x.append(x0)
edge_x.append(x1)
edge_x.append(None)
edge_y.append(y0)
edge_y.append(y1)
edge_y.append(None)
weights.append((round(w["weight"],1), (x0+x1)/2, (y0+y1)/2))
with network.batch_update():
network.data[1].x = node_x
network.data[1].y = node_y
network.data[1].text = list(labels)
network.data[1].marker.color = node_colors
network.data[0].x = edge_x
network.data[0].y = edge_y
network.data[0].text = list(weights)
network.update_layout(xaxis_zeroline=False, yaxis_zeroline=False, xaxis_showgrid=False, yaxis_showgrid=False, plot_bgcolor='rgba(0,0,0,0)')
threshold_choice.observe(plot_corr_graph, names="value")
widgets.VBox([threshold_choice, network])
6.png
网络图显示了高于0.9阈值的股票之间的相关性。
你可以在下面的Colab笔记本中找到重现这些可视化的代码。
总结
热图是显示特征之间相关性的一种快速而简单的方法。然而,对于更大的数据集,解释信息会变得越来越复杂。网络图有更大的优势,因为它把相关的特征分组在一起,并且只显示必要的关联性。也就是说,超过一个特定的阈值。不幸的是,除了使用networkx(或其他图形库)来生成相关矩阵的网络图之外,没有默认的方法来生成。在Plotly中需要对图的布局进行更多的实验,以确保节点和节点群之间有足够的空间,从而使生成的图易于阅读/解释。也许在未来,这将成为Seaborn的一个标准的可视化。在那之前,我们必须要有创造力。
谢谢你的阅读。祝您好运!
资料来源
https://statisticsbyjim.com/regression/multicollinearity-in-regression-analysis/
https://www.analyticsvidhya.com/blog/2021/03/multicollinearity-in-data-science/
https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.corr.html
https://seaborn.pydata.org/generated/seaborn.heatmap.html
https://seaborn.pydata.org/generated/seaborn.clustermap.html
https://networkx.org/
https://www.r-graph-gallery.com/250-correlation-network-with-igraph.html