☰
python的先进制造技术工业场景模拟第六十五篇:导入数控加工批次数据,挖掘造成批次尺寸漂移的工艺因素。
2026/10/5 9:50:35 网站建设 项目流程

周五上午,机加车间质量追溯室。

"这批轴又超差了,"质量工程师阿凯把三坐标报告推过来,"同一把刀、同一程序、同一批毛坯,连续 50 件,前 10 件直径 Φ25.002,到第 50 件变成 Φ25.038,公差带是 ±0.02,后 20 件全废了。客户退了 8 箱。"

我点开机加 MES 导出表。

"这里面有什么?"我问。

"每批次一条汇总 + 每件一条实测,"阿凯说,"主轴转速、进给速度、切深、刀补值、冷却液浓度、环境温湿度、刀具累计加工时长、装夹扭矩、对刀基准偏移、G 代码版本,全记着。但尺寸漂移是'慢慢跑出去的',系统只做单件 SPC 报警,等 Xbar 图出红点,已经跑了 15 件了。"

"我就想干一件事,"阿凯说,"把过去三个月 120 个批次灌进去,自动找出'到底是哪几个工艺因素在推着尺寸往正方向漂'。别跟我说'综合影响',我要能跟工艺组说'主轴温升+刀具磨损+刀补未补偿,这三个解释 82% 的漂移方差,冷却液浓度和装夹扭矩可以不用管'。"

"尺寸漂移不是突变,是累积偏移,"我接话,"像你每天往同一个方向挪桌子 1 毫米,半个月就偏出半米。用 pandas 做批次聚合+特征构造(单件偏差斜率、刀具磨损量、温升积分),numpy 算滑动回归斜率,scikit-learn 的 GradientBoostingRegressor 建模漂移量 + SHAP 风格排列重要性,scipy 做趋势显著性检验(线性回归 p 值 + 相关系数),matplotlib 画漂移曲线+因素热力图+重要性条形+相关网络+残差图+温漂叠加图,networkx 建'工艺因素→尺寸漂移'因果链路。"

"对,"阿凯点头,"要能说清'为什么是温升不是冷却液'。我拿去跟评审说'主轴连续转 4 小时后热伸长 12μm,刀补没跟,尺寸正漂',专家就认这个。"

"用 pandas 做批次特征工程,sklearn GBR 回归+Permutation,scipy 趋势检验,matplotlib 出 6 图,networkx 画因果链,存 results/,"我开工程,"数据自包含,合成 120 批次 × 每批 50 件,下载就能跑。"

敲了行原型:

# 目标: 从多工艺因素中定位驱动尺寸漂移的主因

# 方法: 批次级漂移斜率回归 + GBR + Permutation + 趋势显著性

# 输出: 主因排序 + 漂移量预测 + 补偿建议

"完整版 OOP 封好,"我说,"数据加载器、批次特征工程器、漂移分析器、因素定位器、可视化器、决策链路,输出主因排名+6图+报告。"

阿凯凑近看:"那以后看报告:批次 B073,50 件直径从 +2μm 漂到 +38μm,线性斜率 +0.76μm/件(p<0.001)。主因:主轴热伸长贡献 41%、刀具累计磨损 27%、刀补未补偿 14%,三者合计 82%;冷却液浓度贡献 3%(p=0.41 不显著)。建议:每加工 20 件执行一次热机后刀补补偿,或增加主轴温控回路。"

"对,"我接话,"数控加工不是'测出来超差再返工',是'算出来往哪漂、谁推的、提前补'。数字孪生里挂尺寸漂移敏感度节点,这套就是工艺员的'尺寸罗盘'——不用等 Xbar 图红了才知道跑偏。"

一、实际应用场景(真实痛点)

场景设定:数控加工中,同程序同刀具连续加工批次,尺寸常呈现单向缓慢漂移(多为热伸长导致的正漂或刀具磨损导致的负漂)。传统 SPC 单件控制图对"渐变型偏移"响应滞后,等报警时已成批超差。工艺因素多达 10+ 项,且存在耦合(如主轴温升与运行时长强相关、刀补与磨损强相关),人工归因极难。

现场原话(叙事化):

"不是我们没做质控,"阿凯说,"Xbar-R 图天天在跑,但它是'看单件跳不跳'。尺寸是像爬楼梯一样慢慢上去的,前 30 件都在控制限内,第 31 件刚出限,后面 19 件已经全废了。SPC 救不了渐变漂移。"

"最烦的是归因,"阿凯补充,"工艺组每次开会都吵:有人说冷却液稀了,有人说装夹松了,有人说毛坯硬了。三个说法都有道理,但没人能量化'到底谁推了 80% 的漂移量'。三个月 120 个批次,数据全在 MES 里睡大觉,就等有人把它算明白。"

核心矛盾:"单件 SPC + 经验归因 + 数据沉睡" 与 "批次级漂移建模 + 因素贡献量化 + 趋势显著性检验 + 主动刀补补偿" 之间的断层。

二、痛点分析(映射到滨州职业学院《先进制造技术》课程模型)

《先进制造技术》模块 本篇痛点对应

数控加工与CAD/CAM技术:工艺参数优化与精度控制 尺寸漂移归因 + 刀补策略

先进制造技术基础:公差配合与精度理论 渐变型尺寸偏移 vs 统计公差

智能制造与数字孪生:工艺敏感度建模 热-力耦合漂移因素定位

柔性制造系统FMS与先进生产管理:质量追溯 批次级质量数据回溯

一句话总结:我们需要一个"数控批次数据→单件漂移斜率提取+GBR回归+Permutation重要性+趋势显著性检验+主因定位程序",用

"pandas" 做批次聚合/特征构造,

"numpy" 算滑动回归斜率,

"scikit-learn" GBR+Permutation,

"scipy" 线性回归显著性+相关系数,

"matplotlib" 画漂移曲线+因素热力图+重要性+相关网络+残差+温漂叠加,

"networkx" 建因果链路,实现从"超差再返工"到"数据驱动的主因定位+提前补偿"。

三、核心逻辑讲解(大白话)

3.1 问题本质:把尺寸漂移想成"每天往右挪 1 毫米的桌子"

把一批零件想成你每天在桌上画一条线:

* 单件实测偏差 = 每天画的那条线位置

* 漂移斜率 = 每天往右挪了几毫米(正漂=变大,负漂=变小)

* 主轴温升 = 桌子被晒热了会膨胀

* 刀具磨损 = 笔尖变钝画出来偏左

* 刀补未补偿 = 你知道偏了但没改程序

* 你的目标 = 算出"每天往右挪的那几毫米,主要是晒热贡献的,还是笔钝贡献的"

3.2 业务逻辑 → 代码映射

导入数控批次数据(批次+单件实测)

│

▼ BatchDataLoader (pandas)

读取表:

批次号, 件号, 实测直径, 标称直径,

主轴转速, 进给, 切深, 刀补值,

主轴温度, 刀具累计时长, 冷却液浓度,

环境温湿度, 装夹扭矩, 对刀偏移

│

▼ BatchFeatureEngineer (pandas + numpy)

批次级特征构造:

单件偏差 = 实测 - 标称

漂移斜率 = np.polyfit(件号, 偏差, 1)[0] # μm/件

温升积分 = ∑(主轴温度-室温)×Δt

刀具磨损量 = f(累计时长)

刀补缺口 = 理论补偿量 - 实际刀补值

热伸长估算 = α×L×ΔT

│

▼ DriftAnalyzer (scipy)

趋势显著性:

对每批做线性回归: 偏差~件号

输出斜率 + p值 + R²

标记"显著正漂/显著负漂/稳定"

│

▼ FactorLocator (scikit-learn)

因素定位:

GBR 回归: 用工艺因素预测"漂移斜率"

Permutation Importance → 因素贡献排序

偏相关 → 拆开耦合变量(温升 vs 运行时长)

综合得分 = 0.5×GBR + 0.3×Perm + 0.2×|标准化回归系数|

│

▼ NCVisualizer (matplotlib + networkx)

可视化:

1. 典型批次漂移曲线(实测点+拟合线+公差带)

2. 因素-漂移相关性热力图

3. 因素贡献重要性条形图

4. 主因因果网络(因素→漂移)

5. 模型残差图(验证拟合质量)

6. 温升+磨损+刀补缺口叠加贡献图

│

▼ SyntheticNCBatch (numpy)

合成数据:

120批次 × 每批50件

主因: 主轴温升>刀具磨损>刀补缺口

可复现

3.3 为什么不能"单件 SPC"

视角 问题

Xbar 控制图 对渐变漂移滞后 15-30 件

单因素对比 温升与运行时长共线,分不清

批次斜率回归 直接量化"每件的漂移速度"

因素贡献分解 GBR+Permutation+偏相关三重定位

主动补偿 算出来就补,不等超差

3.4 分析前后对比

维度 传统方式 本程序

漂移发现 超差后报警 斜率显著即预警

归因方式 开会拍脑袋 贡献度量化(p值)

补偿动作 整批返工 每 N 件补刀补

置信度 "大概是温升" "温升贡献41%, p<0.001"

四、OOP 代码实现

4.1 项目结构

nc_drift_mining/

├── nc_drift_mining/

│ ├── __init__.py

│ ├── batch_data_loader.py # 数据加载

│ ├── batch_feature_engineer.py # 批次特征工程

│ ├── drift_analyzer.py # 漂移趋势检验(scipy)

│ ├── factor_locator.py # 因素定位(sklearn)

│ ├── nc_visualizer.py # 可视化

│ └── synthetic_nc_batch.py # 合成数据

├── tests/

│ ├── __init__.py

│ └── test_nc_drift.py

├── results/

│ ├── drift_curve.png

│ ├── factor_heatmap.png

│ ├── factor_importance.png

│ ├── causality_network.png

│ ├── residual_plot.png

│ ├── contribution_stack.png

│ ├── factor_detail.csv

│ └── drift_report.txt

└── run_nc_drift.py

4.2 核心源码

<details>

<summary></summary>

"""数控批次数据加载器"""

import pandas as pd

from pathlib import Path

from typing import Optional

class BatchDataLoader:

"""读取数控加工批次数据(批次+单件实测)"""

def __init__(self, filepath: str = "nc_batch_data.csv",

encoding: str = "utf-8"):

self.filepath = Path(filepath)

self.encoding = encoding

def load(self) -> pd.DataFrame:

if not self.filepath.exists():

raise FileNotFoundError(self.filepath)

df = pd.read_csv(self.filepath, encoding=self.encoding)

req = ["batch_id", "piece_seq", "measured_dia_mm", "nominal_dia_mm"]

miss = [c for c in req if c not in df.columns]

if miss:

raise ValueError(f"缺列: {miss}")

num_cols = ["measured_dia_mm", "nominal_dia_mm", "piece_seq"]

for c in num_cols:

df[c] = pd.to_numeric(df[c], errors="coerce")

df = df.dropna(subset=["measured_dia_mm"]).reset_index(drop=True)

return df

def summary(self, df: pd.DataFrame) -> str:

s = f"批次数量: {df['batch_id'].nunique()}\n"

s += f"单件总数: {len(df)}\n"

s += f"标称直径: {df['nominal_dia_mm'].iloc[0]:.3f} mm\n"

dev = (df["measured_dia_mm"] - df["nominal_dia_mm"]) * 1000

s += f"偏差范围: {dev.min():.1f}μm ~ {dev.max():.1f}μm"

return s

</details>

<details>

<summary></summary>

"""批次级特征工程 (pandas + numpy)"""

import numpy as np

import pandas as pd

from typing import List

class BatchFeatureEngineer:

"""把单件数据聚合成批次级漂移特征"""

def __init__(self, alpha: float = 11.5e-6,

tool_len_mm: float = 120.0,

room_temp: float = 20.0):

self.alpha = alpha # 主轴钢热膨胀系数 1/℃

self.tool_len = tool_len_mm

self.room_temp = room_temp

def add_deviation(self, df: pd.DataFrame) -> pd.DataFrame:

result = df.copy()

result["dev_um"] = (result["measured_dia_mm"] -

result["nominal_dia_mm"]) * 1000.0

return result

def aggregate_batch(self, df: pd.DataFrame) -> pd.DataFrame:

"""每个批次算一条特征"""

df = self.add_deviation(df)

rows = []

for batch_id, g in df.groupby("batch_id"):

g = g.sort_values("piece_seq")

x = g["piece_seq"].values.astype(float)

y = g["dev_um"].values.astype(float)

# 漂移斜率 μm/件

if len(g) >= 3:

slope, intercept = np.polyfit(x, y, 1)

y_fit = slope * x + intercept

ss_res = np.sum((y - y_fit) ** 2)

ss_tot = np.sum((y - y.mean()) ** 2)

r2 = 1 - ss_res / ss_tot if ss_tot > 1e-9 else 0.0

else:

slope, r2 = 0.0, 0.0

# 取批次级工艺参数(取均值或首值)

row = {

"batch_id": batch_id,

"n_pieces": len(g),

"drift_slope_um_per_pc": float(slope),

"drift_r2": float(r2),

"start_dev_um": float(y[0]),

"end_dev_um": float(y[-1]),

"spindle_speed_rpm": g["spindle_speed_rpm"].mean(),

"feed_mm_min": g["feed_mm_min"].mean(),

"depth_of_cut_mm": g["depth_of_cut_mm"].mean(),

"tool_offset_um": g["tool_offset_um"].mean(),

"spindle_temp_c": g["spindle_temp_c"].mean(),

"tool_runtime_h": g["tool_runtime_h"].mean(),

"coolant_pct": g["coolant_pct"].mean(),

"ambient_temp_c": g["ambient_temp_c"].mean(),

"clamp_torque_nm": g["clamp_torque_nm"].mean(),

"align_offset_um": g["align_offset_um"].mean(),

}

# 温升积分

dT = row["spindle_temp_c"] - self.room_temp

row["temp_rise_integral"] = float(dT * row["n_pieces"])

# 热伸长估算 (直径方向, 对称放大2倍)

row["thermal_growth_um"] = float(

2 * self.alpha * self.tool_len * 1e3 * dT)

# 刀具磨损估算(线性近似)

row["tool_wear_um"] = float(0.8 * row["tool_runtime_h"])

# 刀补缺口

row["offset_gap_um"] = float(

row["thermal_growth_um"] + row["tool_wear_um"]

- row["tool_offset_um"])

rows.append(row)

return pd.DataFrame(rows)

def get_factor_columns(self) -> List[str]:

return [

"spindle_temp_c", "tool_runtime_h", "tool_offset_um",

"temp_rise_integral", "thermal_growth_um", "tool_wear_um",

"offset_gap_um", "feed_mm_min", "depth_of_cut_mm",

"coolant_pct", "ambient_temp_c", "clamp_torque_nm",

"align_offset_um", "spindle_speed_rpm",

]

</details>

<details>

<summary></summary>

"""漂移趋势显著性检验 (scipy)"""

import numpy as np

import pandas as pd

from scipy import stats

from typing import Dict

class DriftAnalyzer:

"""对每批做线性回归显著性检验"""

def __init__(self, alpha: float = 0.05):

self.alpha = alpha

def analyze_batch(self, df: pd.DataFrame) -> pd.DataFrame:

"""输入单件级数据, 输出每批趋势判定"""

df = df.copy()

if "dev_um" not in df.columns:

df["dev_um"] = (df["measured_dia_mm"] -

df["nominal_dia_mm"]) * 1000.0

results = []

for batch_id, g in df.groupby("batch_id"):

g = g.sort_values("piece_seq")

x = g["piece_seq"].values.astype(float)

y = g["dev_um"].values.astype(float)

if len(g) < 3:

results.append({"batch_id": batch_id, "slope": 0,

"p_value": 1.0, "r2": 0,

"trend": "insufficient"})

continue

slope, intercept = np.polyfit(x, y, 1)

# scipy 线性回归显著性

res = stats.linregress(x, y)

p_val = res.pvalue

r2 = res.rvalue ** 2

if p_val < self.alpha:

if slope > 0:

trend = "significant_positive_drift"

else:

trend = "significant_negative_drift"

else:

trend = "stable"

results.append({

"batch_id": batch_id,

"slope": float(slope),

"p_value": float(p_val),

"r2": float(r2),

"trend": trend,

})

return pd.DataFrame(results)

def summary_stats(self, batch_df: pd.DataFrame) -> Dict:

n_pos = (batch_df["trend"] == "significant_positive_drift").sum()

n_neg = (batch_df["trend"] == "significant_negative_drift").sum()

n_stable = (batch_df["trend"] == "stable").sum()

return {

"positive_drift_batches": int(n_pos),

"negative_drift_batches": int(n_neg),

"stable_batches": int(n_stable),

"mean_slope": float(batch_df["slope"].mean()),

"max_abs_slope": float(batch_df["slope"].abs().max()),

}

</details>

<details>

<summary></summary>

"""因素定位 (scikit-learn + scipy)"""

import numpy as np

from typing import Dict, List

from sklearn.ensemble import GradientBoostingRegressor

from sklearn.inspection import permutation_importance

from sklearn.model_selection import cross_val_score

from scipy import stats

class FactorLocator:

"""用工艺因素预测漂移斜率, 定位主因"""

def __init__(self, random_state: int = 42):

self.random_state = random_state

self.model_ = None

def fit(self, X: np.ndarray, y: np.ndarray) -> GradientBoostingRegressor:

self.model_ = GradientBoostingRegressor(

n_estimators=200,

learning_rate=0.05,

max_depth=4,

min_samples_leaf=3,

random_state=self.random_state,

)

self.model_.fit(X, y)

return self.model_

def gbr_importance(self, feature_names: List[str]) -> Dict:

imp = dict(zip(feature_names,

self.model_.feature_importances_))

return dict(sorted(imp.items(), key=lambda x: x[1], reverse=True))

def permutation_score(self, X: np.ndarray, y: np.ndarray,

feature_names: List[str]) -> Dict:

perm = permutation_importance(self.model_, X, y,

n_repeats=20,

random_state=self.random_state,

scoring="r2")

result = {}

for i, name in enumerate(feature_names):

result[name] = {

"mean": float(perm.importances_mean[i]),

"std": float(perm.importances_std[i]),

}

return dict(sorted(result.items(),

key=lambda x: x[1]["mean"], reverse=True))

def pearson_with_target(self, X: np.ndarray, y: np.ndarray,

feature_names: List[str]) -> Dict:

"""单变量与漂移斜率的相关性 + p值"""

result = {}

for i, name in enumerate(feature_names):

r, p = stats.pearsonr(X[:, i], y)

result[name] = {"r": float(r), "p_value": float(p)}

return result

def composite_score(self, gbr_imp: Dict, perm_imp: Dict,

corr: Dict, feature_names: List[str]) -> Dict:

"""综合得分 = 0.5×GBR + 0.3×Perm(归一化) + 0.2×|r|"""

# 归一化

gbr_sum = sum(gbr_imp.values()) + 1e-9

gbr_norm = {k: v / gbr_sum for k, v in gbr_imp.items()}

perm_vals = [max(v["mean"], 0) for v in perm_imp.values()]

perm_sum = sum(perm_vals) + 1e-9

perm_norm = {k: max(perm_imp[k]["mean"], 0) / perm_sum

for k in perm_imp}

composite = {}

for feat in feature_names:

r = abs(corr.get(feat, {}).get("r", 0))

score = (0.5 * gbr_norm.get(feat, 0) +

0.3 * perm_norm.get(feat, 0) +

0.2 * r)

p_val = corr.get(feat, {}).get("p_value", 1.0)

composite[feat] = {

"score": float(score),

"gbr_norm": float(gbr_norm.get(feat, 0)),

"perm_norm": float(perm_norm.get(feat, 0)),

"abs_r": float(r),

"p_value": float(p_val),

"significant": p_val < 0.05,

}

return dict(sorted(composite.items(),

key=lambda x: x[1]["score"], reverse=True))

def cross_validate(self, X: np.ndarray, y: np.ndarray,

cv: int = 5) -> Dict:

scores = cross_val_score(self.model_, X, y,

cv=cv, scoring="r2")

return {"r2_mean": float(scores.mean()),

"r2_std": float(scores.std())}

def predict_drift(self, X: np.ndarray) -> np.ndarray:

return self.model_.predict(X)

def recommend_compensation(self, composite: Dict,

top_n: int = 3) -> List[str]:

return [k for k, _ in

list(composite.items())[:top_n]]

</details>

<details>

<summary></summary>

"""可视化 (matplotlib + networkx)"""

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

from pathlib import Path

import networkx as nx

from typing import Dict, List

plt.rcParams["font.sans-serif"] = ["SimHei", "DejaVu Sans"]

plt.rcParams["axes.unicode_minus"] = False

class NCVisualizer:

def __init__(self, results_dir: str = "results"):

self.results_dir = Path(results_dir)

self.results_dir.mkdir(exist_ok=True)

def drift_curve(self, df_pieces: pd.DataFrame, batch_id: str,

tol_um: float = 20.0):

"""典型批次漂移曲线 + 公差带"""

sub = df_pieces[df_pieces["batch_id"] == batch_id].sort_values("piece_seq")

if len(sub) == 0:

sub = df_pieces.head(50)

dev = (sub["measured_dia_mm"] - sub["nominal_dia_mm"]) * 1000

fig, ax = plt.subplots(figsize=(11, 6))

ax.scatter(sub["piece_seq"], dev, c="#3498DB", s=35,

edgecolors="black", label="单件实测偏差", zorder=3)

# 拟合线

x = sub["piece_seq"].values.astype(float)

y = dev.values

slope, intercept = np.polyfit(x, y, 1)

ax.plot(x, slope * x + intercept, "r--", linewidth=2.5,

label=f"拟合斜率 {slope:.3f}μm/件")

ax.axhline(tol_um, color="orange", ls="--", lw=1.8,

label=f"+公差 {tol_um}μm")

ax.axhline(-tol_um, color="orange", ls="--", lw=1.8,

label=f"-公差 {tol_um}μm")

ax.set_xlabel("件号(同批次顺序)", fontsize=12)

ax.set_ylabel("直径偏差 (μm)", fontsize=12)

ax.set_title(f"批次 {batch_id} 尺寸漂移曲线",

fontsize=13, fontweight="bold")

ax.legend(fontsize=10)

ax.grid(alpha=0.3)

plt.tight_layout()

plt.savefig(self.results_dir/"drift_curve.png",

dpi=150, bbox_inches="tight")

plt.close()

def factor_heatmap(self, batch_df: pd.DataFrame,

factor_cols: List[str]):

"""因素-漂移相关性热力图"""

cols = factor_cols + ["drift_slope_um_per_pc"]

corr = batch_df[cols].corr()

fig, ax = plt.subplots(figsize=(12, 10))

im = ax.imshow(corr.values, cmap="RdBu_r", vmin=-1, vmax=1,

aspect="auto")

ax.set_xticks(range(len(cols)))

ax.set_yticks(range(len(cols)))

ax.set_xticklabels(cols, rotation=45, ha="right", fontsize=8)

ax.set_yticklabels(cols, fontsize=8)

for i in range(len(cols)):

for j in range(len(cols)):

v = corr.values[i, j]

c = "white" if abs(v) > 0.6 else "black"

ax.text(j, i, f"{v:.2f}", ha="center", va="center",

color=c, fontsize=7)

plt.colorbar(im, ax=ax, label="Pearson r")

ax.set_title("工艺因素-漂移斜率相关性热力图",

fontsize=13, fontweight="bold")

plt.tight_layout()

plt.savefig(self.results_dir/"factor_heatmap.png",

dpi=150, bbox_inches="tight")

plt.close()

def factor_importance(self, composite: Dict):

"""因素贡献重要性"""

fig, ax = plt.subplots(figsize=(11, 6))

names = list(composite.keys())[:8]

vals = [composite[n]["score"] for n in names]

sig = [composite[n]["significant"] for n in names]

colors = ["#E74C3C" if s else "#95A5A6" for s in sig[::-1]]

ax.barh(range(len(names)), vals[::-1], color=colors,

edgecolor="black", height=0.6)

ax.set_yticks(range(len(names)))

ax.set_yticklabels(names[::-1], fontsize=10)

ax.set_xlabel("综合贡献得分")

ax.set_title("尺寸漂移主因贡献排序(红=显著 p<0.05)",

fontsize=13, fontweight="bold")

ax.grid(axis="x", alpha=0.3)

plt.tight_layout()

plt.savefig(self.results_dir/"factor_importance.png",

dpi=150, bbox_inches="tight")

plt.close()

def causality_network(self, composite: Dict, top_n: int = 5):

"""主因因果网络"""

fig, ax = plt.subplots(figsize=(12, 8))

G = nx.DiGraph()

G.add_node("尺寸漂移", ntype="target")

top = list(composite.items())[:top_n]

for feat, info in top:

G.add_node(feat, ntype="factor")

G.add_edge(feat, "尺寸漂移",

weight=round(info["score"], 3))

pos = nx.spring_layout(G, seed=42, k=2.2)

node_colors = ["#E74C3C" if G.nodes[n]["ntype"] == "target"

else "#3498DB" for n in G.nodes()]

nx.draw_networkx_nodes(G, pos, node_color=node_colors,

node_size=3000, alpha=0.85, ax=ax)

edges = [(u, v) for u, v in G.edges()]

weights = [G[u][v]["weight"] * 8 for u, v in edges]

nx.draw_networkx_edges(G, pos, wid

利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询