← Back to list

Plotting a correlation analysis network graph with Python

Correlation analysis network graph

ZHEMING XU in Top Python Libraries · 2026-06-28 02:44 · 108 claps · 17.0 min read paywalled
#python #data-visualization #data-science #machine-learning #artificial-intelligence
Open on Medium ↗
Wiki topics: ML · Machine Learning AI · AI · General VIS · Visual & Graphic Design EDU · Education & Learning 🔬 · Science · General

Plotting a correlation analysis network graph with Python

Result

Kendall Analysis:

Pearson Analysis:

Spearman Analysis:

This graph illustrates the linear correlation between the feature variables and the target, as well as the correlations among the features themselves. In the circular network, the size of each node visually reflects the strength of its correlation with the target variable; the color of the nodes reflects the value of the correlation coefficient. Meanwhile, the thickness and color of the node borders represent the results of the significance analysis, where nodes with thick black borders indicate statistical significance with the target, and nodes with thin gray borders fail to reach significance levels. The line style of the connections indicates whether a significant relationship exists between the two elements, the thickness of the lines reflects the strength of the correlation, and the color of the network connections represents the actual correlation value.

Code Explanation

  1. Importing libraries and setting fonts
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import networkx as nx
import warnings
from matplotlib.lines import Line2D
from scipy.stats import pearsonr, spearmanr, kendalltau
import matplotlib
matplotlib.rcParams['pdf.fonttype'] = 42
matplotlib.rcParams['ps.fonttype'] = 42
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.serif'] = ['Times New Roman']
plt.rcParams['axes.unicode_minus'] = False
  1. Setting up color libraries and choosing color schemes
COLOR_SCHEMES = {
    1: {'nodes': plt.cm.RdBu_r, 'edges': plt.cm.PRGn},
}
scheme_index =1

current_color_scheme = COLOR_SCHEMES.get(scheme_index, COLOR_SCHEMES[1])
  1. Setting up shape marker libraries and choosing color schemes
STYLE_SCHEMES = {
    1: {'marker': 'o'},
}
style_index = 1

current_style_scheme = STYLE_SCHEMES.get(style_index, STYLE_SCHEMES[1])
  1. Reading data and separating the target variable from feature variables
# Raw data path
file_path = r'mock_data.xlsx'

# Read data
df = pd.read_excel(file_path)

# Target variable
y = df.iloc[:, -1]

# Feature variables
X = df.iloc[:, :-1]

# Get the names of the feature columns and convert them to a list
features = X.columns.tolist()

print(f"Features: {features}")
print(f"Data shape: {X.shape}")
  1. Setting up correlation analysis method libraries and choosing a correlation method
# Correlation analysis methods
CORRELATION_METHODS = {
    1: 'pearson',  # Pearson
    2: 'spearman',  # Spearman
    3: 'kendall'  # Kendall
}

# Set the analysis method to be used
method_index = 3
selected_method_name = CORRELATION_METHODS.get(method_index, 'pearson')  # Get the chosen analysis method

print(f"Currently used correlation analysis method: {selected_method_name.capitalize()}")
  1. Calculate the correlation coefficients and P-values between features and the target, as well as between features themselves
def calculate_corr_p(x, y, method):
    if method == 'pearson':  # Determine which method is being used
        return pearsonr(x, y)  # Compute and return the correlation coefficient and p-value for x and y using the corresponding method
    elif method == 'spearman':
        return spearmanr(x, y)
    elif method == 'kendall':
        return kendalltau(x, y)
    else:
        return pearsonr(x, y)

correlations_target_list = []  # List to store the correlation coefficient between each feature and the target variable
p_target_list = []  # List to store the p-value between each feature and the target variable

for col in features:  # Iterate through each feature column name in the features list
    r, p = calculate_corr_p(X[col], y, selected_method_name)  # Call the function to compute the correlation coefficient and p-value between the current feature and the target variable
    correlations_target_list.append(r)  # Append to the correlation coefficients list
    p_target_list.append(p)  # Append to the p-values list

correlations_target_array = np.array(correlations_target_list)  # Convert the list to a NumPy array for subsequent numerical computations and plotting
p_target_array = np.array(p_target_list)  # Convert the p-values list to a NumPy array

# Absolute correlation (used for node size)
feature_importance_abs = np.abs(correlations_target_array)

# Raw correlation values (used for node color)
feature_importance_signed = correlations_target_array

# Compute pairwise correlations and p-values among features
n_feat = len(features)
corr_matrix_values = np.zeros((n_feat, n_feat))  # Array to store the correlation coefficients between features

# Absolute correlation between features; stronger multicollinearity results in thicker lines
mean_interaction_matrix_abs = np.abs(corr_matrix_values)
np.fill_diagonal(mean_interaction_matrix_abs, 0)  # Ignore self-correlation

# Raw correlation between features, used to control line colors
mean_interaction_matrix_signed = corr_matrix_values
np.fill_diagonal(mean_interaction_matrix_signed, 0)  # Ignore self-correlation
  1. Function definitions and basic settings: obtaining color schemes, shape marker schemes, and creating the canvas
def plot_circular_interaction(features, importance_abs, importance_signed, p_target, interaction_matrix_abs, interaction_matrix_signed, p_matrix):
    # Get color schemes
    cmap_nodes = current_color_scheme['nodes']
    cmap_edges = current_color_scheme['edges']

    # Get node shape markers
    node_marker = current_style_scheme['marker']

    # Create the canvas
    fig, ax = plt.subplots(figsize=(12, 10), subplot_kw={'aspect': 'equal'})

    # Get the number of features
    n_features = len(features)
  1. Building the network graph and layout
    # Create a NetworkX graph object
    G = nx.Graph()

    # Add nodes to the graph
    G.add_nodes_from(features)

    # Generate circular layout coordinates for the nodes
    pos = nx.circular_layout(G)

    # Coordinates for the labels
    label_pos = {k: (v * 1.1) for k, v in pos.items()}
  1. Get correlation coefficients and significance between features, set up color normalization, and prepare to draw network edges
    # Color normalization
    norm_edges = mcolors.Normalize(vmin=interaction_matrix_signed.min(),
                                   vmax=interaction_matrix_signed.max())

    # Base baseline for width/size normalization
    max_interaction_abs = np.max(interaction_matrix_abs)
    max_importance_abs = np.max(importance_abs)

    # Initialize the interaction list
    interactions = []

    # Iterate through features
    for i in range(n_features):
        for j in range(i + 1, n_features):
            # If the absolute strength is greater than 0 (display threshold)
            if strength_abs > 0:
                # Add the interaction pair, absolute strength, raw strength, and p-value to the list
                interactions.append((features[i], features[j], strength_abs, strength_signed, p_val))

    # Sort the interaction list based on absolute strength
    interactions.sort(key=lambda x: x[2])
  1. Draw the network edges between feature variables, setting the line thickness based on the correlation coefficient and the line style based on the results of the significance analysis
    # Iterate through the sorted interaction list
    for u, v, strength_abs, strength_signed, p_val in interactions:
        # Get the line color based on the raw value and the current edge color scheme
        color = cmap_edges(norm_edges(strength_signed))

        # Calculate line thickness based on the absolute value
        width = 0.5 + (strength_abs / max_interaction_abs) * 8

        # Line transparency
        alpha = 0.3 + (strength_abs / max_interaction_abs) * 0.7

        # Draw the edge
        nx.draw_networkx_edges(G,
                               pos,
                               edgelist=[(u, v)],
                               width=width,
                               edge_color=[color],
                               style=current_linestyle,
                               alpha=alpha, ax=ax)
  1. Setting up the mapping logic for node sizes
    # --- Node Processing ---
    # Node color normalization
    norm_nodes = mcolors.Normalize(vmin=importance_signed.min(),
                                   vmax=importance_signed.max())

    # Define the range for node area size
    NODE_SIZE_MIN = 300  # Minimum value, corresponding to the lowest correlation
    NODE_SIZE_MAX = 1000  # Maximum value, corresponding to the highest correlation

    def map_size(value):  # Define a mapping function to linearly convert correlation values to the specified node size range
        if data_max == data_min: return NODE_SIZE_MAX  # If the maximum value equals the minimum value, directly return the preset maximum size
        # Use a linear interpolation formula to map the value into the [NODE_SIZE_MIN, NODE_SIZE_MAX] interval
        return NODE_SIZE_MIN + (value - data_min) / (data_max - data_min) * (NODE_SIZE_MAX - NODE_SIZE_MIN)
  1. Draw nodes, set node size based on the magnitude of the correlation coefficient, set node color, and set node border thickness based on significance
    # Initialize the node colors list
    node_colors = []
    # Initialize the node sizes list
    node_sizes = []

    node_edge_colors = []  # List to store the border color for each node
    node_line_widths = []  # List to store the line width of the border for each node

    # Iterate through each feature
    for i, feat in enumerate(features):
        # Get the raw value for this feature
        if p_val_target < 0.05:
            node_edge_colors.append('black')  # Black border
            node_line_widths.append(2.0)  # Thicken border
        else:
            node_edge_colors.append('grey')  # Grey border
            node_line_widths.append(0.5)  # Thin border

    # Draw nodes
    nx.draw_networkx_nodes(G,  # Draw nodes in graph G
                           pos,  # Coordinate positions
                           node_size=node_sizes,  # Sizes
                           node_color=node_colors,  # Fill colors
                           edgecolors=node_edge_colors,  # Border colors
                           linewidths=node_line_widths,  # Border thicknesses
                           node_shape=node_marker,  # Shape marker
                           ax=ax)
  1. Draw the feature name labels at node positions, and draw the title
    # Iterate through the label positions dictionary
    for node, (x, y) in label_pos.items():
        ha = 'center'  # Horizontal alignment

        # If the x-coordinate is on the right side
        if x > 0.1:
            ha = 'left'  # Set alignment to left

        # If the x-coordinate is on the left side
        elif x < -0.1:
            ha = 'right'  # Set alignment to right

        # Draw the label text
        plt.text(x,
                 y,
                 node,
                 size=12,
                 horizontalalignment=ha,
                 verticalalignment='center')

    # Hide the axes
    ax.axis('off')

    # x-axis limit
    ax.set_xlim(-1.5, 1.5)

    # y-axis limit
    ax.set_ylim(-1.5, 1.5)

    # Title
    plt.title(f'(a) Correlation Network ({selected_method_name.capitalize()})', y=0.95, fontsize=16, weight='bold')
  1. Adding Legends
    # Define the scale values for the line thickness legend
    line_levels = [max_interaction_abs, max_interaction_abs * 0.5, max_interaction_abs * 0.1]

    # Convert values to string labels rounded to two decimal places for the legend display
    line_labels = [f"{val:.2f}" for val in line_levels]
    legend1 = ax.legend(legend_lines,
                        line_labels,
                        loc='center left',
                        bbox_to_anchor=(-0.1, 0.8),
                        title="Feature Correlation\n(Line Width)",
                        title_fontproperties={'weight': 'bold'},
                        frameon=False,
                        labelspacing=1.5)

    # Manually add the first legend object back to the axes
    ax.add_artist(legend1)
  1. Plotting the colorbar and saving the plot results
    # Create a scalar mappable object for edge colors
    sm_edge = plt.cm.ScalarMappable(cmap=cmap_edges, norm=norm_edges)

    # Set an empty array
    sm_edge.set_array([])

    # Draw the colorbar for lines (edges)
    cbar_edge = plt.colorbar(sm_edge, cax=cax_edge)

    # Set the label for the edge colorbar
    cbar_edge.set_label('Interaction Value (Signed)', rotation=270, labelpad=15, fontsize=10, weight='bold')

    # Hide the outline border of the edge colorbar
    cbar_edge.outline.set_visible(False)

    # --- Colorbars ---
    # Position for the node colorbar
    cbar_node_pos = [0.82, 0.20, 0.015, 0.25]

    # Add axes for the node colorbar
    cax_node = fig.add_axes(cbar_node_pos)

    # Create a scalar mappable object for node colors
    sm_node = plt.cm.ScalarMappable(cmap=cmap_nodes, norm=norm_nodes)

    extra_artists = [legend1, legend2, legend3, legend4, cax_edge, cax_node]

    # Save the figures
    save_path_png = fr"{style_index}_scheme{scheme_index}_corr_sig_{selected_method_name}.png"
    save_path_pdf = fr"{style_index}_scheme{scheme_index}_corr_sig_{selected_method_name}.pdf"

    plt.savefig(save_path_png, dpi=300, bbox_inches='tight', bbox_extra_artists=extra_artists)
    plt.savefig(save_path_pdf, bbox_inches='tight', bbox_extra_artists=extra_artists)
  1. Execution section: printing the analysis results and calling the plotting function to generate the figures
if __name__ == "__main__":
    print("-" * 30)
    print("Feature & Target Variable Correlation Ranking")
    print("-" * 30)

    # Create DataFrame object to display analysis results
    df_importance = pd.DataFrame({
        'Feature': features,  # Feature column
        'Correlation (Abs)': feature_importance_abs,  # Importance
        'Correlation (Raw)': feature_importance_signed,  # Direction of influence
        'P-value': p_target_array,  # P-value data
        'Significance': ['**' if p < 0.01 else '*' if p < 0.05 else '-' for p in p_target_array]  # Significance markers
    })

    # Sort in descending order based on importance
    df_importance = df_importance.sort_values(by='Correlation (Abs)', ascending=False)
    print(df_importance.to_string(index=False))
    print("-" * 30)

    print("Inter-feature Multicollinearity / Correlation Strength Ranking")
    print("-" * 30)

    # Initialize an empty list
    interaction_list = []

    # Get the total number of features
    n_features = len(features)

    # Start outer loop
    for i in range(n_features):
        # Start inner loop
        for j in range(i + 1, n_features):
            # Fetch data
            strength = mean_interaction_matrix_abs[i, j]
            direction = mean_interaction_matrix_signed[i, j]
            p_val = p_value_matrix[i, j]  # Get P-value

            # Conditional check
            if strength > 0:
                interaction_list.append({
                    'Feature 1': features[i],
                    'Feature 2': features[j],
                    'Correlation (Abs)': strength,
                    'Correlation (Raw)': direction,
                    'P-value': p_val,  # Record P-value
                    'Significant': 'Yes' if p_val < 0.05 else 'No'
                })

    # Convert to DataFrame
    df_interactions = pd.DataFrame(interaction_list)

    # If not empty
    if not df_interactions.empty:
        # Sort based on correlation strength
        df_interactions = df_interactions.sort_values(by='Correlation (Abs)', ascending=False)
        print(df_interactions.head(15).to_string(index=False))
    else:
        print("No significant correlations found.")

    # Call the plotting function, passing the calculated P-value matrix
    plot_circular_interaction(features,
                              feature_importance_abs,  # Basis for node size
                              feature_importance_signed,  # Basis for node color
                              p_target_array,  # Target significance determines node border thickness
                              mean_interaction_matrix_abs,  # Basis for line thickness
                              mean_interaction_matrix_signed,  # Basis for line color
                              p_value_matrix)  # P-value matrix, used to control line style

Data

[embed]Medium_datasets/Correlation analysis network graph/mock_data.xlsx at main ·… Contribute to checkming00/Medium_datasets development by creating an account on GitHub.github.com

Complete Code

# =========================================================================================
# ====================================== 1. Environment =======================================
# =========================================================================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import networkx as nx
import warnings
from matplotlib.lines import Line2D
from scipy.stats import pearsonr, spearmanr, kendalltau
warnings.filterwarnings("ignore", category=DeprecationWarning)
warnings.filterwarnings("ignore", category=UserWarning)
import matplotlib
matplotlib.rcParams['pdf.fonttype'] = 42
matplotlib.rcParams['ps.fonttype'] = 42
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.serif'] = ['Times New Roman']
plt.rcParams['axes.unicode_minus'] = False

# =========================================================================================
# ======================================2.Color library =======================================
# =========================================================================================
COLOR_SCHEMES = {
    1: {'nodes': plt.cm.RdBu_r, 'edges': plt.cm.PRGn},
    2: {'nodes': plt.cm.PiYG, 'edges': plt.cm.PuOr},
    3: {'nodes': plt.cm.BrBG, 'edges': plt.cm.RdBu_r},
    4: {'nodes': plt.cm.PuOr, 'edges': plt.cm.coolwarm},
    5: {'nodes': plt.cm.RdGy, 'edges': plt.cm.RdYlBu_r},
    6: {'nodes': plt.cm.coolwarm, 'edges': plt.cm.BrBG},
    7: {'nodes': plt.cm.seismic, 'edges': plt.cm.PiYG},
    8: {'nodes': plt.cm.bwr, 'edges': plt.cm.Spectral},
    9: {'nodes': plt.cm.RdYlGn, 'edges': plt.cm.RdBu},
    10: {'nodes': plt.cm.Spectral, 'edges': plt.cm.RdGy},
    11: {'nodes': plt.cm.PRGn, 'edges': plt.cm.RdYlGn},
    12: {'nodes': plt.cm.RdYlBu_r, 'edges': plt.cm.PiYG_r},
    13: {'nodes': plt.cm.PuOr_r, 'edges': plt.cm.bwr},
    14: {'nodes': plt.cm.BrBG_r, 'edges': plt.cm.seismic},
    15: {'nodes': plt.cm.RdBu, 'edges': plt.cm.PuOr},
    16: {'nodes': plt.cm.pink, 'edges': plt.cm.coolwarm},
    17: {'nodes': plt.cm.PiYG, 'edges': plt.cm.BrBG},
    18: {'nodes': plt.cm.RdGy_r, 'edges': plt.cm.GnBu},
    19: {'nodes': plt.cm.Spectral_r, 'edges': plt.cm.RdBu_r},
    20: {'nodes': plt.cm.RdYlGn_r, 'edges': plt.cm.PRGn_r},
}
scheme_index =1

current_color_scheme = COLOR_SCHEMES.get(scheme_index, COLOR_SCHEMES[1])

# =========================================================================================
# ======================================3.Marker library =======================================
# =========================================================================================
STYLE_SCHEMES = {
    1: {'marker': 'o'},
    2: {'marker': r'$\oplus$'},
    3: {'marker': '*'},
    4: {'marker': r'$\odot$'},
    5: {'marker': 'p'},
    6: {'marker': 'h'},
    7: {'marker': r'$\spadesuit$'},
    8: {'marker': r'$\clubsuit$'},
    9: {'marker': r'$\otimes$'},
    10: {'marker': 'X'}
}
style_index = 1

current_style_scheme = STYLE_SCHEMES.get(style_index, STYLE_SCHEMES[1])

# =========================================================================================
# ====================================== 4. Data Loading ==================================
# =========================================================================================
# Raw data path
file_path = r'D:\folder\mock_data.xlsx'

# Read the dataset
df = pd.read_excel(file_path)

# Target variable (last column)
y = df.iloc[:, -1]

# Feature variables (all columns except the last one)
X = df.iloc[:, :-1]

# Get the names of the feature columns and convert them to a list
features = X.columns.tolist()

print(f"Features: {features}")
print(f"Data shape: {X.shape}")

# =========================================================================================
# ================================== 5. Correlation Analysis Setup ========================
# =========================================================================================
# Correlation analysis methods
CORRELATION_METHODS = {
    1: 'pearson',   # Pearson
    2: 'spearman',  # Spearman
    3: 'kendall'    # Kendall
}

# Set the analysis method to be used
method_index = 3
selected_method_name = CORRELATION_METHODS.get(method_index, 'pearson')  # Get the chosen analysis method

print(f"Currently used correlation analysis method: {selected_method_name.capitalize()}")

# =========================================================================================
# ======================= 6. Correlation and P-value Calculation ==========================
# =========================================================================================
def calculate_corr_p(x, y, method):
    if method == 'pearson':  # Check which method is being used
        return pearsonr(x, y)  # Calculate and return the correlation coefficient and p-value for x and y using the corresponding method
    elif method == 'spearman':
        return spearmanr(x, y)
    elif method == 'kendall':
        return kendalltau(x, y)
    else:
        return pearsonr(x, y)

correlations_target_list = []  # List to store the correlation coefficients between each feature and the target variable
p_target_list = []  # List to store the P-values between each feature and the target variable

for col in features:  # Iterate through each feature column name in the feature list
    r, p = calculate_corr_p(X[col], y, selected_method_name)  # Call the function to calculate the correlation coefficient and P-value between the current feature and target variable
    correlations_target_list.append(r)  # Append to the correlation coefficient list
    p_target_list.append(p)  # Append to the P-value list

correlations_target_array = np.array(correlations_target_list)  # Convert the list to a NumPy array for subsequent numerical computations and plotting
p_target_array = np.array(p_target_list)  # Convert the P-value list to a NumPy array

# Absolute correlation (used for node sizes)
feature_importance_abs = np.abs(correlations_target_array)

# Raw correlation values (used for node colors)
feature_importance_signed = correlations_target_array

# Calculate pairwise correlations and P-values between features
n_feat = len(features)
corr_matrix_values = np.zeros((n_feat, n_feat))  # Matrix to store the inter-feature correlation coefficients
p_value_matrix = np.ones((n_feat, n_feat))  # Initialize the P-value matrix, defaulting to 1

for i in range(n_feat):  # Iterate through the number of features as the row index of the correlation matrix
    for j in range(n_feat):  # Iterate through the number of features as the column index of the correlation matrix
        if i == j:  # Check if it is a diagonal element
            corr_matrix_values[i, j] = 1.0  # Correlation coefficient of a feature with itself
            p_value_matrix[i, j] = 0.0  # P-value of a feature with itself
        else:  # If it is not a diagonal element
            r, p = calculate_corr_p(X.iloc[:, i], X.iloc[:, j], selected_method_name)  # Calculate the correlation coefficient and P-value between the i-th and j-th features
            corr_matrix_values[i, j] = r  # Fill the calculated correlation coefficient into the corresponding position in the matrix
            p_value_matrix[i, j] = p  # Fill the calculated P-value into the corresponding position in the matrix

# Absolute values of inter-feature correlations; stronger multicollinearity results in thicker lines
mean_interaction_matrix_abs = np.abs(corr_matrix_values)
np.fill_diagonal(mean_interaction_matrix_abs, 0)  # Ignore self-correlation

# Inter-feature correlations, used to control line colors
mean_interaction_matrix_signed = corr_matrix_values
np.fill_diagonal(mean_interaction_matrix_signed, 0)  # Ignore self-correlation

# =========================================================================================
# ====================================== 7. Plot Function =================================
# =========================================================================================
def plot_circular_interaction(features, importance_abs, importance_signed, p_target, interaction_matrix_abs, interaction_matrix_signed, p_matrix):
    # Get color schemes
    cmap_nodes = current_color_scheme['nodes']
    cmap_edges = current_color_scheme['edges']

    # Get node shape markers
    node_marker = current_style_scheme['marker']

    # Create canvas
    fig, ax = plt.subplots(figsize=(12, 10), subplot_kw={'aspect': 'equal'})

    # Get the number of features
    n_features = len(features)

    # Create a NetworkX graph object
    G = nx.Graph()

    # Add nodes to the graph
    G.add_nodes_from(features)

    # Generate circular layout coordinates for nodes
    pos = nx.circular_layout(G)

    # Coordinates for labels
    label_pos = {k: (v * 1.1) for k, v in pos.items()}

    # Color normalization
    norm_edges = mcolors.Normalize(vmin=interaction_matrix_signed.min(),
                                   vmax=interaction_matrix_signed.max())

    # Normalization baseline for width/size
    max_interaction_abs = np.max(interaction_matrix_abs)
    max_importance_abs = np.max(importance_abs)

    # Initialize the interaction list
    interactions = []

    # Iterate through features
    for i in range(n_features):
        for j in range(i + 1, n_features):
            # Interaction strength between two features
            strength_abs = interaction_matrix_abs[i, j]
            # Interaction direction between two features
            strength_signed = interaction_matrix_signed[i, j]
            # Get P-value
            p_val = p_matrix[i, j]
            # If absolute strength is greater than 0 (display threshold)
            if strength_abs > 0:
                # Add the interaction pair, absolute strength, raw strength, and P-value to the list
                interactions.append((features[i], features[j], strength_abs, strength_signed, p_val))

    # Sort the interaction list based on absolute strength
    interactions.sort(key=lambda x: x[2])

    # Iterate through the sorted interaction list
    for u, v, strength_abs, strength_signed, p_val in interactions:
        # Get the line color based on the raw value and the current edge color scheme
        color = cmap_edges(norm_edges(strength_signed))
        # Calculate line thickness based on the absolute value
        width = 0.5 + (strength_abs / max_interaction_abs) * 8
        # Line transparency
        alpha = 0.3 + (strength_abs / max_interaction_abs) * 0.7
        # Set line style based on P-value
        if p_val < 0.05:
            current_linestyle = '-'  # Significant: Solid line
        else:
            current_linestyle = '--'  # Not Significant: Dashed line

        # Draw the edge
        nx.draw_networkx_edges(G,
                               pos,
                               edgelist=[(u, v)],
                               width=width,
                               edge_color=[color],
                               style=current_linestyle,
                               alpha=alpha, ax=ax)

    # --- Node Processing ---
    # Node color normalization
    norm_nodes = mcolors.Normalize(vmin=importance_signed.min(),
                                   vmax=importance_signed.max())

    # Define the range for node area size
    NODE_SIZE_MIN = 300  # Minimum value, corresponding to the lowest correlation
    NODE_SIZE_MAX = 1000  # Maximum value, corresponding to the highest correlation

    data_min = np.min(importance_abs)  # Minimum value of absolute correlation data, used as lower bound of mapping interval
    data_max = np.max(importance_abs)  # Maximum value of absolute correlation data, used as upper bound of mapping interval

    def map_size(value):  # Define a mapping function to linearly convert correlation values to the specified node size range
        if data_max == data_min: return NODE_SIZE_MAX  # If the maximum value equals the minimum value, directly return the preset maximum size
        # Use a linear interpolation formula to map the value into the [NODE_SIZE_MIN, NODE_SIZE_MAX] interval
        return NODE_SIZE_MIN + (value - data_min) / (data_max - data_min) * (NODE_SIZE_MAX - NODE_SIZE_MIN)

    # Initialize the node colors list
    node_colors = []
    # Initialize the node sizes list
    node_sizes = []

    node_edge_colors = []  # List to store the border color for each node
    node_line_widths = []  # List to store the line width of the border for each node

    # Iterate through each feature
    for i, feat in enumerate(features):
        # Get the raw value for this feature
        imp_sign = importance_signed[i]
        # Get the absolute importance for this feature
        imp_abs = importance_abs[i]
        p_val_target = p_target[i]  # Correlation P-value between the current feature and target variable

        # Determine border style based on P-value
        if p_val_target < 0.05:
            node_edge_colors.append('black')  # Black border
            node_line_widths.append(2.0)  # Thicken border
        else:
            node_edge_colors.append('grey')  # Grey border
            node_line_widths.append(0.5)  # Thin border

        # Compute and add node color
        node_colors.append(cmap_nodes(norm_nodes(imp_sign)))
        # Compute and add node size
        node_sizes.append(map_size(imp_abs))

    # Draw nodes
    nx.draw_networkx_nodes(G,  # Draw nodes in graph G
                           pos,  # Coordinate positions
                           node_size=node_sizes,  # Sizes
                           node_color=node_colors,  # Fill colors
                           edgecolors=node_edge_colors,  # Border colors
                           linewidths=node_line_widths,  # Border thicknesses
                           node_shape=node_marker,  # Shape marker
                           ax=ax)

    # Iterate through the label positions dictionary
    for node, (x, y) in label_pos.items():
        ha = 'center'  # Horizontal alignment
        # If the x-coordinate is on the right side
        if x > 0.1:
            ha = 'left'  # Set alignment to left
        # If the x-coordinate is on the left side
        elif x < -0.1:
            ha = 'right'  # Set alignment to right

        # Draw the label text
        plt.text(x,
                 y,
                 node,
                 size=12,
                 horizontalalignment=ha,
                 verticalalignment='center')

    # Hide the axes
    ax.axis('off')

    # x-axis limit
    ax.set_xlim(-1.5, 1.5)

    # y-axis limit
    ax.set_ylim(-1.5, 1.5)

    # Title
    plt.title(f'(a) Correlation Network ({selected_method_name.capitalize()})', y=0.95, fontsize=16, weight='bold')

    # Define the scale values for the line thickness legend
    line_levels = [max_interaction_abs, max_interaction_abs * 0.5, max_interaction_abs * 0.1]
    # Convert values to string labels rounded to two decimal places for the legend display
    line_labels = [f"{val:.2f}" for val in line_levels]

    # Initialize an empty list to store custom line legend handle objects
    legend_lines = []
    # Iterate through each scale value to generate corresponding legend lines
    for val in line_levels:
        # Calculate line width based on the current value
        w = 0.5 + (val / max_interaction_abs) * 8
        legend_lines.append(Line2D([0], [0], color='black', linewidth=w, linestyle='-'))  # Legend solid line displays thickness

    # First legend object: line thickness legend
    legend1 = ax.legend(legend_lines,
                        line_labels,
                        loc='center left',
                        bbox_to_anchor=(-0.1, 0.8),
                        title="Feature Correlation\n(Line Width)",
                        title_fontproperties={'weight': 'bold'},
                        frameon=False,
                        labelspacing=1.5)
    # Manually add the first legend object back to the axes
    ax.add_artist(legend1)

    # Significance line legend
    # Define the handle list for the significance legend
    legend_sig_lines = [
        # Create a solid line handle representing significance
        Line2D([0], [0], color='black', linewidth=2, linestyle='-', label='P < 0.05'),
        # Create a dashed line handle representing non-significance
        Line2D([0], [0], color='black', linewidth=2, linestyle='--', label='P ≥ 0.05')
    ]
    # Create the significance legend
    legend2 = ax.legend(handles=legend_sig_lines,
                        loc='center left',  # Centered left
                        bbox_to_anchor=(-0.1, 0.6),  # Legend position
                        title="Significance\n(Line Style)",  # Legend title
                        title_fontproperties={'weight': 'bold'},  # Legend title font
                        frameon=False,  # Remove legend border
                        labelspacing=1.5)  # Vertical spacing
    # Manually add to the axes
    ax.add_artist(legend2)

    # --- Node Size Legend ---
    legend_vals = [data_max, (data_max + data_min) / 2, data_min]  # Select maximum, middle, and minimum data values as three reference points for the legend
    node_labels = [f"{val:.2f}" for val in legend_vals]  # Format values as strings rounded to two decimal places
    legend_nodes = []  # List to store custom legend handles
    for val in legend_vals:  # Iterate through reference values
        area_size = map_size(val)  # Calculate the corresponding node area based on the mapping function
        diameter_size = np.sqrt(area_size)  # Convert area square root to diameter, because Line2D's markersize parameter uses diameter/width
        # Create Line2D object
        legend_nodes.append(
            Line2D([0], [0],
                   marker=node_marker,  # Node shape
                   color='w',  # Line color
                   markerfacecolor='black',  # Fill color
                   markersize=diameter_size,  # Size
                   linestyle='None')  # Do not display connection line
        )
    legend3 = ax.legend(legend_nodes,  # Pass custom icon list
                        node_labels,  # Pass corresponding label text list
                        loc='center left',  # Centered left
                        bbox_to_anchor=(-0.1, 0.36),  # Precise position
                        title="Target Correlation\n(Node Size)",  # Legend title
                        title_fontproperties={'weight': 'bold'},  # Title font
                        frameon=False,  # Remove legend border
                        labelspacing=2.5)  # Vertical spacing
    ax.add_artist(legend3)  # Manually add this legend object to the axes

    # --- Node Significance Legend ---
    legend_sig_nodes = [
        # Legend icon representing significance
        Line2D([0], [0], marker=node_marker, color='w', markerfacecolor='white', markeredgecolor='black', markeredgewidth=2, markersize=10, label='P < 0.05'),
        # Legend icon representing non-significance
        Line2D([0], [0], marker=node_marker, color='w', markerfacecolor='white', markeredgecolor='grey', markeredgewidth=0.5, markersize=10, label='P ≥ 0.05')
    ]
    legend4 = ax.legend(handles=legend_sig_nodes,  # Pass the predefined custom handle list above
                        loc='center left',  # Centered left
                        bbox_to_anchor=(-0.1, 0.15),  # Precise position
                        title="Node Significance",  # Legend title
                        title_fontproperties={'weight': 'bold'},  # Title font
                        frameon=False,  # Remove border
                        labelspacing=1.5)  # Vertical spacing
    ax.add_artist(legend4)  # Manually add the legend object to the axes

    # --- Colorbars ---
    # Define edge colorbar position: left, bottom, width, height
    cbar_edge_pos = [0.82, 0.55, 0.015, 0.25]
    # Create a new axis explicitly for the colorbar
    cax_edge = fig.add_axes(cbar_edge_pos)
    # Create a scalar mappable object for edge colors
    sm_edge = plt.cm.ScalarMappable(cmap=cmap_edges, norm=norm_edges)
    # Set an empty array
    sm_edge.set_array([])
    # Draw the colorbar for lines (edges)
    cbar_edge = plt.colorbar(sm_edge, cax=cax_edge)
    # Set the label for the edge colorbar
    cbar_edge.set_label('Interaction Value (Signed)', rotation=270, labelpad=15, fontsize=10, weight='bold')
    # Hide the outline border of the edge colorbar
    cbar_edge.outline.set_visible(False)

    # --- Colorbars ---
    # Position for the node colorbar
    cbar_node_pos = [0.82, 0.20, 0.015, 0.25]
    # Add axes for the node colorbar
    cax_node = fig.add_axes(cbar_node_pos)
    # Create a scalar mappable object for node colors
    sm_node = plt.cm.ScalarMappable(cmap=cmap_nodes, norm=norm_nodes)
    # Set an empty array
    sm_node.set_array([])
    # Draw the node colorbar
    cbar_node = plt.colorbar(sm_node, cax=cax_node)
    # Set the label for the node colorbar
    cbar_node.set_label('Feature Value (Signed)', rotation=270, labelpad=15, fontsize=10, weight='bold')
    # Hide the outline border of the node colorbar
    cbar_node.outline.set_visible(False)

    extra_artists = [legend1, legend2, legend3, legend4, cax_edge, cax_node]

    # Save the figures
    save_path_png = fr"D:\folder\{style_index}_scheme{scheme_index}_corr_sig_{selected_method_name}.png"
    save_path_pdf = fr"D:\folder\{style_index}_scheme{scheme_index}_corr_sig_{selected_method_name}.pdf"
    plt.savefig(save_path_png, dpi=300, bbox_inches='tight', bbox_extra_artists=extra_artists)
    plt.savefig(save_path_pdf, bbox_inches='tight', bbox_extra_artists=extra_artists)

if __name__ == "__main__":
    print("-" * 30)
    print("Feature & Target Variable Correlation Ranking")
    print("-" * 30)

    # Create DataFrame object to display analysis results
    df_importance = pd.DataFrame({
        'Feature': features,  # Feature column
        'Correlation (Abs)': feature_importance_abs,  # Importance
        'Correlation (Raw)': feature_importance_signed,  # Direction of influence
        'P-value': p_target_array,  # P-value data
        'Significance': ['**' if p < 0.01 else '*' if p < 0.05 else '-' for p in p_target_array]  # Significance markers
    })

    # Sort in descending order based on importance
    df_importance = df_importance.sort_values(by='Correlation (Abs)', ascending=False)
    print(df_importance.to_string(index=False))
    print("-" * 30)

    print("Inter-feature Multicollinearity / Correlation Strength Ranking")
    print("-" * 30)

    # Initialize an empty list
    interaction_list = []

    # Get the total number of features
    n_features = len(features)

    # Start outer loop
    for i in range(n_features):
        # Start inner loop
        for j in range(i + 1, n_features):
            # Fetch data
            strength = mean_interaction_matrix_abs[i, j]
            direction = mean_interaction_matrix_signed[i, j]
            p_val = p_value_matrix[i, j]  # Get P-value

            # Conditional check
            if strength > 0:
                interaction_list.append({
                    'Feature 1': features[i],
                    'Feature 2': features[j],
                    'Correlation (Abs)': strength,
                    'Correlation (Raw)': direction,
                    'P-value': p_val,  # Record P-value
                    'Significant': 'Yes' if p_val < 0.05 else 'No'
                })

    # Convert to DataFrame
    df_interactions = pd.DataFrame(interaction_list)

    # If not empty
    if not df_interactions.empty:
        # Sort based on correlation strength
        df_interactions = df_interactions.sort_values(by='Correlation (Abs)', ascending=False)
        print(df_interactions.head(15).to_string(index=False))
    else:
        print("No significant correlations found.")

    # Call the plotting function, passing the calculated P-value matrix
    plot_circular_interaction(features,
                              feature_importance_abs,  # Basis for node size
                              feature_importance_signed,  # Basis for node color
                              p_target_array,  # Target significance determines node border thickness
                              mean_interaction_matrix_abs,  # Basis for line thickness
                              mean_interaction_matrix_signed,  # Basis for line color
                              p_value_matrix)  # P-value matrix, used to control line style

Thank you for reading.


메타데이터
post_id
3c2faa854ce7
slug
plotting-a-correlation-analysis-network-graph-with-python-3c2faa854ce7
url
https://medium.com/top-python-libraries/plotting-a-correlation-analysis-network-graph-with-python-3c2faa854ce7
canonical_url
https://medium.com/top-python-libraries/plotting-a-correlation-analysis-network-graph-with-python-3c2faa854ce7
author_url
https://medium.com/@benjamin_hui
status
ok
fetched_at
2026-07-09 15:12:33