python的先进制造技术工业场景模拟第三十三篇:读取机器人离线编程点位数据集,统计各段轨迹运动距离,估算运动耗时。
周五上午,焊接机器人工作站。
"这条产线,6 台六轴机器人跑弧焊,点位都是离线编程软件导出来的,"工艺员小郑把一份 CSV 拖到桌面上,"每个轨迹段由一串示教点组成,点里有 XYZ 坐标和关节角度。我想知道每段轨迹实际走了多长、大概花多少时间,哪段最耗时,能不能优化路径顺序把节拍压下来。"
我点开数据。
"这表里有什么?"小郑问。
"每条记录是一个轨迹点,带 segment_id(轨迹段编号)、point_seq(段内序号)、x/y/z 坐标、进给速度档位,"我指着屏幕,"问题是它只给点,不给距离,也不给时间。距离得相邻点做欧氏距离累加,时间得用距离除以该段设定进给速度。现在离线软件能出总路径长,但拆不到段,也关联不上实际节拍。"
"我就想干一件事,"小郑说,"把每个轨迹段的运动距离算出来,按段内设定速度估算耗时,排个序,画出来。如果某段距离只有 200mm 却花了 1.2 秒,说明速度被限了,可能是靠近工件防碰撞降速;如果某段距离 800mm 但耗时跟 200mm 那段差不多,说明速度档用高了,得复核是不是会甩弧。"
"比如段 S-03,12 个点,折线累计 386mm,设定进给 300mm/s,"我接话,"理论耗时 1.29s,但实际离线仿真给的是 1.8s,多出来的 0.5s 是加减速补偿。六轴机器人走折线不是匀速滑过去,每个拐点都要减速再加速,这段拐点多,所以耗时偏差大。"
"对,"小郑点头,"还有,我想看各段耗时的占比,整条程序总节拍是多少,机器人之间有没有某台路径特别长导致等件。最好还能把轨迹段按'距离-耗时特征'聚个类,看看哪些是长直段、哪些是密集折线段、哪些是微动调整段。"
"用 pandas 按 segment_id 分组算折线距离,numpy 向量化算欧氏距离和加减速补偿,matplotlib 画段距离条形图+耗时饼图+轨迹散点,scipy 做距离分布检验,scikit-learn 聚类轨迹段画像,networkx 建段-点关系网,"我开工程,"数据自包含,合成一批离线编程点位数据,下载就能跑。"
敲了行原型:
seg = df.groupby("segment_id").apply(
lambda g: np.sum(np.linalg.norm(
g[["x","y","z"]].diff().dropna().values, axis=1))
)
est_time = seg / feed_speed + corner_penalty
"完整版用 OOP 封好,"我说,"一个类管点位加载,一个类算段距离,一个类估算耗时(含加减速模型),一个类做统计检验,一个类聚类轨迹段,一个类建段-点关系网,一个类出图。输出段距离表、耗时表、聚类结果,存 results/。"
小郑凑近看:"那以后看报告:8 个轨迹段,总距离 2.84m,理论总耗时 9.6s,含加减速补偿后 11.3s。S-05 段距离最短(120mm)但单位距离耗时最高,因为是 9 个微折点绕焊缝起弧位;S-02 是长直段 760mm,耗时 2.5s 最稳。聚类分了三类:长直段 / 折线段 / 微动段。6 台机器人里 3 号机总路径最长,比均值多 18%,查了是离线程序没做路径合并,重复走了回程空走。合并后整线节拍从 11.3s 压到 9.8s。"
"对,"我接话,"轨迹不是看点数,是看折线长度+拐点损耗+速度档匹配。数字孪生里要建机器人节拍仿真模型,这些段级耗时就是校准基准。"
一、实际应用场景(真实痛点)
场景设定:多机器人弧焊/搬运工作站,轨迹由离线编程软件(OLP)导出为点位序列。工艺员需要按轨迹段量化运动距离与估算耗时,识别空走路径、拐点损耗段、限速段,优化程序顺序与速度档,压低整线节拍。
现场原话(叙事化):
"不是我们不会编路径,"小郑说,"是以前编完就直接下机,靠示教器跑一遍看节拍。示教器显示总周期 12 秒,但说不清哪段占了 5 秒。有次客户要求节拍压到 10 秒,我们改了三天速度档,最后发现是某段回程空走了 400mm,离线软件里根本没标红。"
"还有拐点问题,"小郑补充,"焊接轨迹绕角的地方,机器人每个拐点都要减速,离线软件按直线算时间,实际慢了 30%。这种'理论时间 vs 实际时间'的偏差,不按段拆开算,永远对不上。"
核心矛盾:"离线编程点位流水" 与 "段级距离聚合 + 耗时估算(含加减速) + 段画像聚类 + 多机路径均衡 + 节拍优化" 之间的断层。
二、痛点分析(映射到滨州职业学院《先进制造技术》课程模型)
《先进制造技术》模块 本篇痛点对应
工业机器人技术基础:轨迹规划、运动学、节拍计算 段级轨迹距离与耗时估算
数控加工与CAD/CAM技术:刀路/路径优化思想迁移 路径合并与空走消除
智能制造与数字孪生:机器人运动仿真模型 段级耗时为孪生节拍模型提供基准
FMS与先进生产管理:多机协同节拍平衡 多机器人路径长度均衡
先进制造新模式:数据驱动工艺优化 聚类识别路径模式指导重构
一句话总结:我们需要一个"机器人离线编程轨迹段距离与耗时估算程序",用
"pandas" 按段聚合点位,
"numpy" 算欧氏距离与加减速补偿,
"matplotlib" 画距离条形图/耗时饼图/轨迹散点,
"scipy" 做距离分布检验,
"scikit-learn" 聚类轨迹段画像,
"networkx" 建段-点关系网,实现从"点位序列"到"段距离 + 耗时估算 + 模式聚类 + 多机均衡"。
三、核心逻辑讲解(大白话)
3.1 问题本质:把轨迹想成"快递员送件路线"
把机器人轨迹想成快递员在车间里走路线:
* 点位 = 路线上的打卡点(A点→B点→C点)
* 轨迹段 = 一段任务(比如"绕这个焊缝走一圈")
* 距离 = 两点之间直线走多远(折线累加,不是首尾直线)
* 耗时 = 距离 ÷ 速度,但每个拐角要刹车再加速,得加补偿时间
* 长直段 = 高速路(可以跑满速)
* 折线段 = 老城区巷子(拐弯多,得慢)
* 微动段 = 原地调整姿势(走几毫米也要减速)
* 整线节拍 = 所有段耗时加起来
* 多机均衡 = 几个快递员别有的累死有的闲着
3.2 业务逻辑 → 代码映射
导入离线编程点位数据
│
▼ TrajLoader (pandas)
读取 CSV:
robot_id, segment_id, point_seq, x, y, z,
feed_speed, joint_config(可选)
排序+校验坐标
│
▼ SegmentDistanceCalculator (numpy/pandas)
段距离计算:
段内相邻点欧氏距离累加
L = Σ √(Δx²+Δy²+Δz²)
│
▼ TimeEstimator (numpy)
耗时估算:
直线耗时 = L / feed_speed
拐点补偿 = 拐点数 × 减速加速时间
总耗时 = 直线耗时 + 拐点补偿
│
▼ TrajStatistics (scipy)
统计检验:
段距离分布正态性(Shapiro)
多机器人总距离 ANOVA / t 检验
│
▼ SegmentProfiler (scikit-learn)
轨迹段聚类:
特征=[距离, 耗时, 拐点密度, 单位距离耗时]
KMeans → 长直段/折线段/微动段
│
▼ TrajGraph (networkx)
段-点关系网:
节点=段/点
边=顺序连接, 边权=段内距离
│
▼ TrajVisualizer (matplotlib)
可视化:
1. 各段距离条形图
2. 各段耗时占比饼图
3. 轨迹三维散点投影(XY平面+颜色表段)
4. 聚类散点图(距离 vs 单位距离耗时)
5. 段-点关系网
│
▼ SyntheticTrajGenerator (numpy)
合成数据:
多机器人×多段, 含长直/折线/微动三类
含空走回程段(可优化)
3.3 为什么不能只看总路径长
视角 问题
总距离 2.8m 掩盖某段 120mm 却耗时最长
理论时间 = 距离/速度 忽略拐点加减速,偏差 30%
段级拆解+拐点补偿 真实耗时逼近示教器实测
聚类分模式 知道该优化哪类段
3.4 分析前后对比
维度 离线软件导出 本程序
段距离 仅总长 逐段累加
耗时估算 直线模型 含拐点加减速
路径模式 无 聚类分类
多机均衡 无 统计对比
优化方向 凭经验 数据指向空走段/限速段
四、OOP 代码实现
4.1 项目结构
robot_traj_analysis/
├── robot_traj_analysis/
│ ├── __init__.py
│ ├── traj_loader.py # 点位加载
│ ├── distance_calculator.py # 段距离计算
│ ├── time_estimator.py # 耗时估算(含加减速)
│ ├── traj_statistics.py # 统计检验(scipy)
│ ├── segment_profiler.py # 轨迹段聚类(sklearn)
│ ├── traj_graph.py # 段-点关系网(networkx)
│ ├── visualizer.py # 可视化
│ └── synthetic_data.py # 合成数据
├── tests/
│ ├── __init__.py
│ └── test_traj_analysis.py
├── results/
│ ├── segment_distance_bar.png
│ ├── segment_time_pie.png
│ ├── traj_xy_scatter.png
│ ├── segment_cluster.png
│ ├── traj_network.png
│ ├── segment_metrics.csv
│ ├── robot_summary.csv
│ ├── segment_clusters.csv
│ └── traj_report.txt
└── run_traj_analysis.py
4.2 核心源码
<details>
<summary></summary>
"""机器人离线编程点位加载器"""
import pandas as pd
from pathlib import Path
from typing import Optional
class TrajLoader:
"""加载离线编程导出的点位CSV"""
def __init__(self, filepath: str = "traj_points.csv",
encoding: str = "utf-8"):
self.filepath = Path(filepath)
self.encoding = encoding
self._raw: Optional[pd.DataFrame] = None
def load(self) -> pd.DataFrame:
if not self.filepath.exists():
raise FileNotFoundError(f"文件不存在: {self.filepath}")
self._raw = pd.read_csv(self.filepath, encoding=self.encoding)
rename = {}
for tgt, al in {
"robot_id": ["robot_id", "机器人", "robot"],
"segment_id": ["segment_id", "轨迹段", "seg"],
"point_seq": ["point_seq", "点序号", "seq"],
"x": ["x", "X坐标", "x_coord"],
"y": ["y", "Y坐标", "y_coord"],
"z": ["z", "Z坐标", "z_coord"],
"feed_speed": ["feed_speed", "进给速度", "speed"],
}.items():
if tgt not in self._raw.columns:
for a in al:
if a in self._raw.columns:
rename[a] = tgt
break
self._raw = self._raw.rename(columns=rename)
req = ["segment_id", "point_seq", "x", "y", "z"]
miss = [c for c in req if c not in self._raw.columns]
if miss:
raise ValueError(f"缺少必要列: {miss}")
for c in ["x", "y", "z", "feed_speed"]:
if c in self._raw.columns:
self._raw[c] = pd.to_numeric(self._raw[c], errors="coerce")
self._raw = self._raw.dropna(subset=["x", "y", "z"]).copy()
self._raw["robot_id"] = self._raw.get("robot_id", "R-01").fillna("R-01").astype(str).str.strip()
self._raw["segment_id"] = self._raw["segment_id"].astype(str).str.strip()
self._raw["point_seq"] = pd.to_numeric(self._raw["point_seq"], errors="coerce").fillna(0).astype(int)
# 按段内序号排序
self._raw = self._raw.sort_values(
["robot_id", "segment_id", "point_seq"]
).reset_index(drop=True)
# 默认进给速度
if "feed_speed" not in self._raw.columns:
self._raw["feed_speed"] = 300.0
self._raw["feed_speed"] = self._raw["feed_speed"].fillna(300.0)
return self._raw
</details>
<details>
<summary></summary>
"""轨迹段距离计算 (numpy)"""
import numpy as np
import pandas as pd
from typing import Optional
class SegmentDistanceCalculator:
"""按段计算折线累计距离与拐点信息"""
def __init__(self):
pass
def segment_metrics(self, df: pd.DataFrame) -> pd.DataFrame:
"""计算每段距离、点数、拐点数"""
rows = []
for (robot, seg), g in df.groupby(["robot_id", "segment_id"]):
pts = g[["x", "y", "z"]].values.astype(float)
if len(pts) < 2:
dist = 0.0
corners = 0
else:
diffs = np.diff(pts, axis=0)
seg_lens = np.linalg.norm(diffs, axis=1)
dist = float(np.sum(seg_lens))
# 拐点: 相邻两段方向夹角 > 15度
corners = self._count_corners(diffs, angle_thr_deg=15.0)
feed = g["feed_speed"].iloc[0]
rows.append({
"robot_id": robot,
"segment_id": seg,
"point_count": len(g),
"distance_mm": round(dist, 2),
"feed_speed": feed,
"corner_count": corners,
"corner_density": round(corners / max(1, dist/100), 2),
})
return pd.DataFrame(rows).sort_values(
["robot_id", "segment_id"]
).reset_index(drop=True)
def _count_corners(self, diffs: np.ndarray, angle_thr_deg: float) -> int:
"""统计方向变化超过阈值的拐点"""
if len(diffs) < 2:
return 0
v1 = diffs[:-1]
v2 = diffs[1:]
norms1 = np.linalg.norm(v1, axis=1)
norms2 = np.linalg.norm(v2, axis=1)
mask = (norms1 > 1e-6) & (norms2 > 1e-6)
if not mask.any():
return 0
cosang = np.sum(v1 * v2, axis=1) / (norms1 * norms2 + 1e-9)
cosang = np.clip(cosang, -1.0, 1.0)
angles = np.degrees(np.arccos(cosang))
return int(np.sum((angles > angle_thr_deg) & mask))
</details>
<details>
<summary></summary>
"""轨迹耗时估算 (含加减速补偿)"""
import numpy as np
import pandas as pd
from typing import Optional
class TimeEstimator:
"""
耗时模型:
t_linear = distance_mm / (feed_speed * 1000) 秒
t_corner = corner_count * corner_penalty 秒
t_total = t_linear + t_corner
"""
def __init__(self, corner_penalty: float = 0.08,
accel_comp_factor: float = 1.05):
"""
corner_penalty: 每个拐点加减速补偿时间(秒)
accel_comp_factor: 直线段加减速整体补偿系数
"""
self.corner_penalty = corner_penalty
self.accel_comp_factor = accel_comp_factor
def estimate(self, metrics_df: pd.DataFrame) -> pd.DataFrame:
"""基于段指标估算耗时"""
out = metrics_df.copy()
# 直线耗时(秒): mm / (mm/s)
out["t_linear_s"] = (
out["distance_mm"] / out["feed_speed"] * self.accel_comp_factor
).round(4)
out["t_corner_s"] = (
out["corner_count"] * self.corner_penalty
).round(4)
out["t_total_s"] = (
out["t_linear_s"] + out["t_corner_s"]
).round(4)
out["time_per_mm_ms"] = (
out["t_total_s"] * 1000 / out["distance_mm"].replace(0, np.nan)
).round(2)
out["time_per_mm_ms"] = out["time_per_mm_ms"].fillna(0)
return out
def robot_total(self, est_df: pd.DataFrame) -> pd.DataFrame:
"""按机器人汇总"""
rows = []
for robot, g in est_df.groupby("robot_id"):
rows.append({
"robot_id": robot,
"total_distance_mm": round(g["distance_mm"].sum(), 2),
"total_time_s": round(g["t_total_s"].sum(), 3),
"segment_count": len(g),
"total_corners": int(g["corner_count"].sum()),
"avg_feed": round(g["feed_speed"].mean(), 1),
})
return pd.DataFrame(rows).sort_values("total_time_s", ascending=False).reset_index(drop=True)
</details>
<details>
<summary></summary>
"""轨迹统计检验 (scipy)"""
import numpy as np
import pandas as pd
from scipy import stats
from typing import Dict, Optional
class TrajStatistics:
"""段距离分布 + 多机对比"""
def __init__(self, alpha: float = 0.05):
self.alpha = alpha
def distance_normality(self, est_df: pd.DataFrame) -> Dict:
"""段距离正态性检验"""
d = est_df["distance_mm"].values
if len(d) < 3:
return {"shapiro_stat": np.nan, "shapiro_p": np.nan}
if len(d) > 5000:
d = d[:5000]
stat, p = stats.shapiro(d)
return {
"shapiro_stat": round(float(stat), 4),
"shapiro_p": round(float(p), 4),
"is_normal": bool(p > self.alpha),
"mean_mm": round(float(np.mean(d)), 2),
"std_mm": round(float(np.std(d, ddof=1)), 2),
}
def robot_distance_anova(self, est_df: pd.DataFrame) -> Dict:
"""多机器人总距离 ANOVA"""
groups = []
for _, g in est_df.groupby("robot_id"):
if len(g) > 1:
groups.append(g["distance_mm"].values)
if len(groups) < 2:
return {"f_statistic": np.nan, "p_value": np.nan,
"significant": False}
f_stat, p_val = stats.f_oneway(*groups)
return {
"f_statistic": round(float(f_stat), 4),
"p_value": round(float(p_val), 4),
"significant": bool(p_val < self.alpha),
}
def time_efficiency_test(self, est_df: pd.DataFrame) -> pd.DataFrame:
"""各段单位距离耗时统计"""
rows = []
for _, r in est_df.iterrows():
rows.append({
"segment_id": r["segment_id"],
"distance_mm": r["distance_mm"],
"t_total_s": r["t_total_s"],
"time_per_mm_ms": r["time_per_mm_ms"],
})
return pd.DataFrame(rows).sort_values("time_per_mm_ms", ascending=False).reset_index(drop=True)
</details>
<details>
<summary></summary>
"""轨迹段画像聚类 (scikit-learn)"""
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from typing import Optional
class SegmentProfiler:
"""基于距离/耗时/拐点密度的轨迹段聚类"""
def __init__(self, n_clusters: int = 3, random_state: int = 42):
self.n_clusters = n_clusters
self.random_state = random_state
def profile(self, est_df: pd.DataFrame) -> pd.DataFrame:
df = est_df.copy()
feat_cols = ["distance_mm", "t_total_s", "corner_count", "time_per_mm_ms"]
for c in feat_cols:
if c not in df.columns:
df[c] = 0.0
X = df[feat_cols].fillna(0).values
scaler = StandardScaler()
Xs = scaler.fit_transform(X)
km = KMeans(n_clusters=self.n_clusters, random_state=self.random_state)
df["cluster"] = km.fit_predict(Xs)
centers = scaler.inverse_transform(km.cluster_centers_)
label_map = {}
for i, c in enumerate(centers):
dist, t_total, corners, tpm = c
if dist > 500 and corners < 3:
label_map[i] = "长直段"
elif tpm > 1.5:
label_map[i] = "微动调整段"
else:
label_map[i] = "密集折线段"
df["segment_pattern"] = df["cluster"].map(label_map)
return df
def cluster_summary(self, df: pd.DataFrame) -> pd.DataFrame:
if "segment_pattern" not in df.columns:
return pd.DataFrame()
rows = []
for pat, g in df.groupby("segment_pattern"):
rows.append({
"pattern": pat,
"count": len(g),
"avg_distance_mm": round(g["distance_mm"].mean(), 1),
"avg_time_s": round(g["t_total_s"].mean(), 3),
"avg_time_per_mm_ms": round(g["time_per_mm_ms"].mean(), 2),
"segments": ", ".join(g["segment_id"].tolist()[:8]),
})
return pd.DataFrame(rows).sort_values("avg_distance_mm", ascending=False).reset_index(drop=True)
</details>
<details>
<summary></summary>
"""段-点关系网 (networkx)"""
import networkx as nx
import pandas as pd
import numpy as np
from typing import Optional
class TrajGraph:
"""建机器人-段-点顺序关系网"""
def __init__(self):
self.G = nx.DiGraph()
def build(self, df: pd.DataFrame, est_df: Optional[pd.DataFrame] = None) -> nx.DiGraph:
self.G.clear()
for (robot, seg), g in df.groupby(["robot_id", "segment_id"]):
rnode = f"R:{robot}"
snode = f"S:{robot}_{seg}"
self.G.add_node(rnode, ntype="robot")
self.G.add_node(snode, ntype="segment")
self.G.add_edge(rnode, snode, weight=1)
pts = g.sort_values("point_seq")
prev = snode
for _, r in pts.iterrows():
pnode = f"P:{robot}_{seg}_{r['point_seq']}"
self.G.add_node(pnode, ntype="point",
pos=(r["x"], r["y"], r["z"]))
self.G.add_edge(prev, pnode, weight=1)
prev = pnode
# 段距离标注
if est_df is not None:
sub = est_df[(est_df["robot_id"]==robot) & (est_df["segment_id"]==seg)]
if not sub.empty:
self.G.nodes[snode]["distance_mm"] = sub["distance_mm"].iloc[0]
self.G.nodes[snode]["time_s"] = sub["t_total_s"].iloc[0]
return self.G
def heavy_segments(self) -> pd.DataFrame:
"""按距离排序的重载段"""
rows = []
for n, d in self.G.nodes(data=True):
if d.get("ntype") == "segment" and "distance_mm" in d:
rows.append({
"segment": n.split(":")[-1],
"distance_mm": d["distance_mm"],
"time_s": d.get("time_s", 0),
})
df = pd.DataFrame(rows)
if df.empty:
return df
return df.sort_values("distance_mm", ascending=False).reset_index(drop=True)
</details>
<details>
<summary></summary>
"""可视化 (matplotlib)"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
plt.rcParams["font.sans-serif"] = ["SimHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False
class TrajVisualizer:
def __init__(self, results_dir: str = "results"):
self.results_dir = Path(results_dir)
self.results_dir.mkdir(exist_ok=True)
def distance_bar(self, est_df):
"""各段距离条形图"""
fig, ax = plt.subplots(figsize=(12, 6))
labels = est_df["segment_id"].astype(str)
colors = plt.cm.viridis(est_df["distance_mm"] / max(est_df["distance_mm"]+1e-6))
ax.bar(range(len(est_df)), est_df["distance_mm"], color=colors, edgecolor="white")
ax.set_xticks(range(len(est_df)))
ax.set_xticklabels(labels, rotation=45, fontsize=8)
ax.set_ylabel("距离 (mm)")
ax.set_title("各轨迹段运动距离", fontsize=14, fontweight="bold")
ax.grid(axis="y", alpha=0.3)
plt.tight_layout()
plt.savefig(self.results_dir / "segment_distance_bar.png",
dpi=150, bbox_inches="tight")
plt.close()
def time_pie(self, est_df):
"""各段耗时占比饼图"""
fig, ax = plt.subplots(figsize=(9, 9))
ax.pie(est_df["t_total_s"], labels=est_df["segment_id"].astype(str),
autopct="%1.1f%%", startangle=90,
colors=plt.cm.Set3(np.linspace(0,1,len(est_df))),
wedgeprops={"edgecolor":"white"})
ax.set_title("各轨迹段耗时占比", fontsize=14, fontweight="bold")
plt.tight_layout()
plt.savefig(self.results_dir / "segment_time_pie.png",
dpi=150, bbox_inches="tight")
plt.close()
def xy_scatter(self, df, est_df):
"""XY平面轨迹散点(颜色表段)"""
fig, ax = plt.subplots(figsize=(11, 9))
segs = df["segment_id"].unique()
cmap = plt.cm.tab10(np.linspace(0,1,len(segs)))
for i, s in enumerate(segs):
sub = df[df["segment_id"]==s].sort_values("point_seq")
ax.plot(sub["x"], sub["y"], "-o", color=cmap[i%len(cmap)],
markersize=3, linewidth=1.2, label=f"段{s}", alpha=0.85)
ax.set_xlabel("X (mm)")
ax.set_ylabel("Y (mm)")
ax.set_title("机器人轨迹XY投影(按段着色)", fontsize=14, fontweight="bold")
ax.set_aspect("equal", adjustable="datalim")
ax.legend(fontsize=7, ncol=2)
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(self.results_dir / "traj_xy_scatter.png",
dpi=150, bbox_inches="tight")
plt.close()
def cluster_scatter(self, df):
"""聚类散点: 距离 vs 单位距离耗时"""
if "segment_pattern" not in df.columns:
return
fig, ax = plt.subplotsfigsize=(10, 7))
patterns = df["segment_pattern"].unique()
cmap = plt.cm.Set1(np.linspace(0,1,len(patterns)))
for i, pat in enumerate(patterns):
sub = df[df["segment_pattern"]==pat]
ax.scatter(sub["distance_mm"], sub["time_per_mm_ms"],
c=[cmap[i]], label=pat, s=60, alpha=0.8,
edgecolors="black", linewidths=0.5)
for _, r in sub.iterrows():
ax.annotate(r["segment_id"], (r["distance_mm"]
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)