回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序很高兴得知该插件可在 Mac 上运行。
你是对的,我忘记在使加载项尽可能通用后重新添加“sprint”过滤器。
以下代码:
- 使用加载项从 Strava 获取新数据
- 过滤行以仅包含“冲刺”名称
- 过滤列以匹配“2022_03_28 Strava Sprinting Training Data Set 5 Runs”数据集中发布的内容
- 识别图案
- 将脚本添加到数据表以绘制结果图表
names default to here(1);
// if add-in is found, get fresh data from Strava, otherwise just use current data table
if( file exists("$ADDIN_HOME(com.caes.ngambles.strava-get-activities)\main.jsl"),
include("$ADDIN_HOME(com.caes.ngambles.strava-get-activities)\main.jsl");
wait(0);
);
// get reference to current data table
dt = current data table();
// Clean up data set for specific motif extraction challenge
if( contains( dt << get column names(string), "name"),
r = dt << get rows where(
!contains(lower case(:name), "sprint")
);
try(dt << delete rows(r));
);
colList = {
"name",
"altitude",
"distance",
"grade_smooth",
"lat",
"lng"
};
for( k = 1, k <= n items(colList), k++,
try(dt << delete column( colList(k) ));
);
// Find Regions that match target shapes
dt << New Column( "Shape Match",
Numeric,
nominal,
Formula(
thresh = 12500;
shapeList = {
{221, 245, 289, 327, 374, 431, 477, 518, 535, 518, 491, 451, 401, 350, 304, 238, 178, 161, 156, 106, 96, 96, 87, 85, 81, 79, 76, 74, 73, 72, 73, 72, 72, 72, 72, 72, 71, 70, 70, 70},
{306, 321, 347, 384, 429, 476, 515, 546, 579, 582, 576, 542, 489, 442, 375, 348, 295, 275, 242, 185, 152, 121, 106, 98, 93, 89, 89, 85, 84, 83, 82, 82, 81, 80, 79, 79, 78, 78, 78, 78},
{357, 390, 401, 428, 475, 505, 558, 604, 619, 634, 634, 625, 581, 530, 483, 444, 378, 357, 324, 282, 214, 186, 162, 153, 137, 132, 124, 109, 104, 97, 94, 90, 90, 89, 88, 87, 87, 85, 85, 86}
};
minErr = Empty();
For( k = 1, k <= N Items( shapeList ), k++,
err = 0;
For( n = 0, n < N Items( shapeList[k] ), n++,
For( m = 1, m <= N Items( shapeList[k] ), m++,
err += (:watts[(Row() + m) - n - 1] - shapeList[k][m]) ^ 2
);
err /= N Items( shapeList[k] ) - 1;
minErr = Minimum( minErr, err );
);
);
If( minErr <= thresh,
flag = 1,
flag = 0
);
flag;
)
);
// Run Region
dt << New Column( "Run Region",
Numeric,
"Continuous",
Formula(
regionCount = Summation( k = 0, 5, Lag( :Shape Match, k ) );
If( regionCount < 6 & region Count > 0 & :Shape Match == 1,
nearStart = 1,
nearStart = 0
);
If( nearStart == 0,
ans = :Shape Match;
,
ans = 0;
If( :watts[row()] > 0 & :watts[row() +1] > 0,
ans = :Shape Match;
)
);
ans;
)
);
// motif ID
dt << New Column( "Motif ID",
Numeric,
Nominal,
Formula(
mID = 0;
For( k = 1, k <= Row(), k++,
If( :Run Region[k - 1] == 0 & :Run Region[k] == 1,
mID++
)
);
If( :Run Region == 1, mID );
)
);
// row state formula column
dt << New Column("Row State Formula",
Row State,
Row State,
Formula(
If(
Is Missing(:Motif ID),
Row State() = Combine States(Hidden State(1), Excluded State(1)),
Row State()
)
)
);
// linear interpolation of zero values within motif ID
dt << New Column( "watts modified",
Numeric,
Continuous,
Formula(
If( Is Missing( :Motif ID ),
Empty(),
If( :watts > 0,
:watts,
rb = Row();
ra = Row();
For( k = Row(), k >= 1, k--,
If( :watts[k] != 0,
rb = k;
Break();
)
);
For( k = Row(), k <= N Rows(), k++,
If( :watts[k] != 0,
ra = k;
Break();
)
);
deltaPower = :watts[rb] - :watts[ra];
deltaTime = rb - ra;
:watts[rb] + (deltaPower / deltaTime) * (Row() - rb);
)
)
)
);
// rank timestamp, Motif ID - x axis values to help graph each motif
dt << New Column( "X",
Numeric,
Continuous,
Formula( Col Rank( :timestamp, :Motif ID ) )
);
// graph results script added to data table
dt << new script(
"Graph Results",
names default to here(1);
dt = current data table();
nw = new window(
"Motif Extraction Results",
h list box(
gb1 = dt << Graph Builder(
Show Control Panel( 0 ),
Show Legend( 0 ),
Show Footer( 0 ),
Variables(
X( :Name( "X" ) ),
Y( :watts modified ),
Wrap( :Motif ID )
),
Elements( Line( X, Y, Legend( 6 ) ), Points( X, Y, Legend( 7 ) ) )
)
,
gb2 = dt << Graph Builder(
Show Control Panel( 0 ),
Show Footer( 0 ),
Variables( X( :X ), Y( :watts modified ), Overlay( :Motif ID ) ),
Elements(
Smoother( X, Y, Legend( 5 ), Lambda( 0.0014 ) ),
Points( X, Y, Legend( 6 ) )
)
)
)
);
);
// run script to make graphs
// eval( dt << get property("Graph Results") );回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序它在 Mac 上确实有效,但是,如果您输入的刷新令牌不正确,加载项将拒绝加载,您必须卸载并重新安装。 这是在拉动所有最近的活动吗? 最好有搜索字符串来只提取那些具有感兴趣标题(即“sprint”)的字符串。
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序我无法在 Mac 上对其进行测试,因此我的默认选择仅适用于 Windows。附件是一个允许 Windows 和 Mac 的插件。 我不知道它不应该工作的原因,但它未经测试。
如果它在 Mac 上成功运行,请告诉我。
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序有什么理由这在 macOS 上不起作用吗? 我刚刚尝试加载它,您可能只为 Windows 创建了它。
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序我一直在思考我应对这一挑战的最初方法,并认为我可以做得更好,所以我进行了第二次尝试。
这一次,我定义了几个目标形状,然后搜索数据以寻找形状匹配得很好的区域。 我定义的目标形状是:

此方法需要较少的调整参数,并通过允许轻松添加额外的目标形状来提供更大的灵活性。
我添加了一个列(修改了瓦特),当瓦特值在运行期间下降时执行线性外推。
我还添加了一个列(行状态公式),它会自动隐藏和排除与主题 ID 无关的数据点。
在我看来,这是对我最初应对挑战的解决方案的改进。
该方法从“2022_03_28 Strava Sprinting Training Data Set 5 Runs.jmp”数据集中识别出 41 次跑步

处理 Strava 数据的 JSL 代码是:
names default to here(1);
<BR />// uncomment the following two lines if you have installed the Strava - Get Activities add-in and want to automatically download the Strava data prior to identifying motifs<BR />// include("$ADDIN_HOME(com.caes.ngambles.strava-get-activities)\main.jsl");<BR />// wait(0);<BR />
dt = current data table();
// Find Regions that match target shapes
dt << New Column( "Shape Match",
Numeric,
nominal,
Formula(
thresh = 12500;
shapeList = {{221, 245, 289, 327, 374, 431, 477, 518, 535, 518, 491, 451, 401, 350, 304, 238, 178, 161, 156, 106, 96, 96, 87, 85, 81, 79, 76,
74, 73, 72, 73, 72, 72, 72, 72, 72, 71, 70, 70, 70}, {306, 321, 347, 384, 429, 476, 515, 546, 579, 582, 576, 542, 489, 442, 375, 348, 295,
275, 242, 185, 152, 121, 106, 98, 93, 89, 89, 85, 84, 83, 82, 82, 81, 80, 79, 79, 78, 78, 78, 78}, {357, 390, 401, 428, 475, 505, 558, 604,
619, 634, 634, 625, 581, 530, 483, 444, 378, 357, 324, 282, 214, 186, 162, 153, 137, 132, 124, 109, 104, 97, 94, 90, 90, 89, 88, 87, 87, 85,
85, 86}};
minErr = Empty();
For( k = 1, k <= N Items( shapeList ), k++,
err = 0;
For( n = 0, n < N Items( shapeList[k] ), n++,
For( m = 1, m <= N Items( shapeList[k] ), m++,
err += (:watts[(Row() + m) - n - 1] - shapeList[k][m]) ^ 2
);
err /= N Items( shapeList[k] ) - 1;
minErr = Minimum( minErr, err );
);
);
If( minErr <= thresh,
flag = 1,
flag = 0
);
)
);
// Run Region - helps clean up regions found by shape match
dt << New Column( "Run Region",
Numeric,
"Continuous",
Formula(
regionCount = Summation( k = 0, 5, Lag( :Shape Match, k ) );
If( regionCount < 6 & region Count > 0 & :Shape Match == 1,
nearStart = 1,
nearStart = 0
);
If( nearStart == 0,
ans = :Shape Match;
,
ans = 0;
If( :watts[row()] > 0 & :watts[row() +1] > 0,
ans = :Shape Match;
)
);
ans;
)
);
// motif ID
dt << New Column( "Motif ID",
Numeric,
Nominal,
Formula(
mID = 0;
For( k = 1, k <= Row(), k++,
If( :Run Region[k - 1] == 0 & :Run Region[k] == 1,
mID++
)
);
If( :Run Region == 1, mID );
)
);
// row state formula column
dt << New Column("Row State Formula",
Row State,
Row State,
Formula(
If(
Is Missing(:Motif ID),
Row State() = Combine States(Hidden State(1), Excluded State(1)),
Row State()
)
)
);
// linear interpolation of zero values within motif ID
dt << New Column( "watts modified",
Numeric,
Continuous,
Formula(
If( Is Missing( :Motif ID ),
Empty(),
If( :watts > 0,
:watts,
rb = Row();
ra = Row();
For( k = Row(), k >= 1, k--,
If( :watts[k] != 0,
rb = k;
Break();
)
);
For( k = Row(), k <= N Rows(), k++,
If( :watts[k] != 0,
ra = k;
Break();
)
);
deltaPower = :watts[rb] - :watts[ra];
deltaTime = rb - ra;
:watts[rb] + (deltaPower / deltaTime) * (Row() - rb);
)
)
)
);
// rank timestamp, Motif ID - x axis values to help graph each motif
dt << New Column( "X",
Numeric,
Continuous,
Formula( Col Rank( :timestamp, :Motif ID ) )
);
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序我很高兴听到瓦特通过 API 为您提供。
我对 Strava API 代码进行了一些更新,这些代码可以自动处理获取刷新的访问代码,然后我将其汇总到我附加的通用插件中。
第一次运行插件时,系统会提示你输入 Strava API 信息

它会保存在您的加载项文件夹中,因此您不必再次输入。
该插件返回一个数据表,其中包含 Strava 的所有可用活动。

这个插件应该进一步简化从 Strava API 获取数据的过程,不仅适用于这个项目,也适用于其他也想从 Strava 探索数据的人。
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序极好的@ngambles . 这很好用,也感谢您发布视频链接,其中描述了如何获取访问令牌。 这些数据是我希望看到的。 此外,与手动导出它们并将它们组合到 .csv 文件相比,这是一个很好的改进。 真正为自动化数据提取和清理数据以进行分析并定期发布到 JMP Live 奠定了基础。 是的,瓦特是通过您脚本中的 API 为我提供的。

我贴进去@brady_brady上面的公式,它仍然没有得到所有的间隔。 然而,由于@ngambles的 Strava API 代码,我们在数据集中有另一个有趣的特性可以探索和使用,它是平滑的速度列。 我认为这很有用。 您现在还可以使用唯一的活动 ID 作为另一个功能(知道我只会在任何给定活动中运行 6-10 个冲刺。 我认为我们应该再次使用 ML 模型, @Byron_JMP ,现在我们有了更大的训练数据集和一些额外的功能来构建模型。
我附上了更新的训练数据集。

和@Jordan_Hiller我觉得你也应该试试! 您是数据清理专家。
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序我试过直接从 Strava API 获取数据。我成功获得了距离、速度、位置、高度和坡度的测量值。 但是我没有得到功率测量值。我怀疑这是由于我的 Strava 帐户级别和/或由于我缺少功率测量设备。 我有理由相信我的代码可能适用于在 Strava 数据中包含功率数据的用户,但我无法确认。 如果这个解决方案不正确,我无法想象它离需要的东西太远了。
我找到了这个Youtube 视频对于开始使用 Strava API 非常有用。 它清楚地演示了如何从 Strava API 获取访问令牌,这是 JSL 代码从 Strava 获取任何信息之前所必需的。 在下面的代码中,将 your_access_token_here 替换为您在运行之前从 Strava API 获得的访问令牌。
names default to here(1);
accessToken = "your_access_token_here"; // access token from the Strava API
// helpful link to get started with the API: https://www.youtube.com/watch?v=sgscChKfGyg
//////////////////////////////////////////
//////////////////////////////////////////
// get list of activities from Strava
s = New HTTP Request(
URL( "https://www.strava.com/api/v3/athlete/activities?access_token=" || accessToken ),
Method( "GET" ),
Headers( {"Accept: application/json"} )
) << Send;
dtx = json to data table(s);
dtx << set name("Activities List");
// keep only activites marked as sprint
r = dtx << get rows where(
!contains(lowercase(:name), "sprint")
);
dtx << delete rows(r);
if( n rows(dtx) == 0,
close(dtx, no save);
throw();
);
// get list of unique activity ids
summarize( activityList = by(dtx:id));
// get and combine the data from each activity
dty = new table();
dty << set name("Strava Data_temp");
for( k = 1, k <= n items(activityList), k++,
actStream = New HTTP Request(
URL( "https://www.strava.com/api/v3/activities/" || activityList[k] || "/streams?keys=latlng,watts,distance,velocity,altitude,velocity_smooth,cadence,temp,moving,grade_smooth&access_token=" || accessToken ),
Method( "GET" ),
Headers( {"Accept: application/json"} )
) << Send;
dtz = json to data table(actStream);
dtz << new column("Activity ID", numeric, nominal, set each value(num(activityList[k])));
dty << concatenate(dtz, append to first table);
close(dtz, no save);
);
// add rank column for x value
dty << New Column( "x",
Numeric,
"Continuous",
Formula( Col Rank( :Activity ID, :type, :Activity ID ) )
);
// split up latitude and longitude pairs
myCount = 0;
for( k = 1, k <= n rows(dty), k++,
if( dty:type[k] != "latlng",
dty:type[k] = dty:type[k]
,
myCount ++;
if( modulo(myCount,2) == 0,
dty:type[k] = "lat",
dty:type[k] = "lng"
)
);
);
// split tall table to wider table
dt = dty << Split(
Split By( :type ),
Split( :data ),
Group( :x, :Activity ID ),
Remaining Columns( Drop All ),
Sort by Column Property
);
// done with this data table
close(dty, no save);
// update start time from activities table
dt << Update(
With( dtx ),
Match Columns( :Activity ID = :id ),
Add Columns from Update Table( :start_date, :name ),
Replace Columns in Main Table( :start_date, :name )
);
// close activity list
close(dtx, no save);
// clean up time stamp
dt << Begin Data Update;
dt << Recode Column(
dt:start_date,
{Regex( _rcNow, ".*^(.*)Z.*", "\1", GLOBALREPLACE )},
Target Column( :start_date )
);
dt << End Data Update;
dt:start_date << data type(numeric) << Format( "yyyy-mm-ddThh:mm:ss", 19, 0 ) << Input Format( "yyyy-mm-ddThh:mm:ss", 0 );
dt << New Column( "timestamp",
Numeric,
"Continuous",
Format( "yyyy-mm-ddThh:mm:ss", 19, 0 ),
Input Format( "yyyy-mm-ddThh:mm:ss", 0 ),
Formula( (:start_date + :x) - 1 )
);
wait(0);
dt:timestamp << delete formula;
dt << delete column("start_date");
// arrange columns
dt << Move Selected Columns( {:timestamp}, To first );
dt << Move Selected Columns( {:Activity ID, :name}, after(:timestamp) );
// set name
dt << set name("Strava Data");
// sort chronologically
dt << Sort( By( :timestamp ), Order( Ascending ), replace table );
dt << sort( by(:Activity ID), order( ascending ), replace table );代码:
- 查看所有 Strava 活动并仅选择名称中带有“sprint”的活动(不区分大小写)
- 试图获得许多测量值,包括功率(瓦特)
- 将信息组织在类似于本次挑战中提供的原始数据集的数据表中
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序我试着直接从 Strava API 获取数据。 我成功地获得了几个测量值,包括距离、速度、经度、纬度、高度和坡度。 但是我没有得到力量。 我怀疑这可能是因为我的 Strava 帐户和/或我缺少测量功率的设备。 这个解决方案可能是也可能不是那些在 Strava 帐户中拥有电源信息的人所需要的,但如果它不正确,我无法想象我离得太远了。
要开始使用 Strava API,这YouTube视频非常有帮助,它清楚地演示了获取访问令牌的步骤,这是此代码从 Strava 获取任何数据之前所必需的。 在下面的代码中,将 your_access_token_here 替换为您的实际访问令牌。
names default to here(1);
accessToken = "your_access_token_here"; // access token from the Strava API
// helpful link to get started with the API: https://www.youtube.com/watch?v=sgscChKfGyg
//////////////////////////////////////////
//////////////////////////////////////////
// get list of activities from Strava
s = New HTTP Request(
URL( "https://www.strava.com/api/v3/athlete/activities?access_token=" || accessToken ),
Method( "GET" ),
Headers( {"Accept: application/json"} )
) << Send;
dtx = json to data table(s);
dtx << set name("Activities List");
// keep only activities marked as sprint
r = dtx << get rows where(
!contains(lowercase(:name), "sprint")
);
dtx << delete rows(r);
if( n rows(dtx) == 0,
close(dtx, no save);
throw();
);
// get list of unique activity ids
summarize( activityList = by(dtx:id));
// get and combine the data streams from each activity
dty = new table();
dty << set name("Strava Data_temp");
for( k = 1, k <= n items(activityList), k++,
actStream = New HTTP Request(
URL( "https://www.strava.com/api/v3/activities/" || activityList[k] || "/streams?keys=latlng,watts,distance,velocity,altitude,velocity_smooth,cadence,temp,moving,grade_smooth&access_token=" || accessToken ),
Method( "GET" ),
Headers( {"Accept: application/json"} )
) << Send;
dtz = json to data table(actStream);
dtz << new column("Activity ID", numeric, nominal, set each value(num(activityList[k])));
dty << concatenate(dtz, append to first table);
close(dtz, no save);
);
// add rank column for x value
dty << New Column( "x",
Numeric,
"Continuous",
Formula( Col Rank( :Activity ID, :type, :Activity ID ) )
);
// split up latitude and longitude pairs
myCount = 0;
for( k = 1, k <= n rows(dty), k++,
if( dty:type[k] != "latlng",
dty:type[k] = dty:type[k]
,
myCount ++;
if( modulo(myCount,2) == 0,
dty:type[k] = "lat",
dty:type[k] = "lng"
)
);
);
// split tall table to wider table
dt = dty << Split(
Split By( :type ),
Split( :data ),
Group( :x, :Activity ID ),
Remaining Columns( Drop All ),
Sort by Column Property
);
// done with this data table
close(dty, no save);
// update start time from activities table
dt << Update(
With( dtx ),
Match Columns( :Activity ID = :id ),
Add Columns from Update Table( :start_date, :name ),
Replace Columns in Main Table( :start_date, :name )
);
// close activity list
close(dtx, no save);
// clean up time stamp
dt << Begin Data Update;
dt << Recode Column(
dt:start_date,
{Regex( _rcNow, ".*^(.*)Z.*", "\1", GLOBALREPLACE )},
Target Column( :start_date )
);
dt << End Data Update;
dt:start_date << data type(numeric) << Format( "yyyy-mm-ddThh:mm:ss", 19, 0 ) << Input Format( "yyyy-mm-ddThh:mm:ss", 0 );
dt << New Column( "timestamp",
Numeric,
"Continuous",
Format( "yyyy-mm-ddThh:mm:ss", 19, 0 ),
Input Format( "yyyy-mm-ddThh:mm:ss", 0 ),
Formula( (:start_date + :x) - 1 )
);
wait(0);
dt:timestamp << delete formula;
dt << delete column("start_date");
// arrange columns
dt << Move Selected Columns( {:timestamp}, To first );
dt << Move Selected Columns( {:Activity ID, :name}, after(:timestamp) );
// set name
dt << set name("Strava Data");
// sort chronologically
dt << Sort( By( :timestamp ), Order( Ascending ), replace table );
dt << sort( by(:Activity ID), order( ascending ), replace table );
代码:
- 仅包括名称中包含“sprint”(不区分大小写)的活动的数据
- 试图获得功率(瓦特),以及许多其他测量值,包括距离、速度、位置、高度等......
- 以类似于本次挑战中提供的原始数据表的格式排列数据
我希望此代码适用于在其 Strava 帐户中拥有电源信息的用户。
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序很好,布雷迪。
关于从间隔开始到退出的一些相当短的时间的好点。 然而,辍学似乎最多为 2-3 秒。 我们将在本周收集的新数据中看到这一点。
附件是一些额外的训练数据。 这大约是 1:40 的非常稳定的状态运行,但速度更快(接近我的临界功率的 100%)。 可能有帮助@Byron_JMP , 不过,使用他的 ML 模型。 也许向这个训练数据集添加一个特征,使其更接近热身/热身范围?
还有谁想解决使用 HTTP Request () 通过 Strava API 直接获取数据的问题;在 JMP 中? 我知道@dieter_pisot以前做过这个。 我可以用冲刺之类的东西标记锻炼,以便可以通过 API 轻松检索数据拉取。

回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序这是另一种对丢失进行插值的方法,使用一些(相对简单的)公式列,其中许多可以通过使用更复杂的插值公式来消除,但代价是容易排除故障和编写公式。 (公式和图形脚本见附表)
结果似乎与我们想要的一致,至少在示例数据集中:(红色点将被删除)


以下是它的工作原理,因此您可以判断所做的假设是否适用于您的数据:
首先,由于“丢失”而发生的零插值。 选择了线性插值;考虑了样条曲线和二次方程,但结果不如我的眼睛。
请注意下面第 4-6 行中“插值幂”列中的“后面的零点”。
“Cum Zeros Behind”是对零的包容性回顾。
“Cum Zeros Ahead”是对零的包容性展望。
“零块”是块中连续零的数量。
“Groups for Zeros”从 1 开始,并在出现新的一组零时增加
当有一个短的(少于 7 个)零块而不是从第 1 行开始时,就会发生插值。
****注意:在示例数据中,最长的插值情况跨越 4 个零,而间隔之间最短的“真实”中断是 7 个零。 这是一个非常小的错误余地,并且可能是这种方法的一个重大缺点。 重要的是最短的“真实”中断要长于最长的“丢失”。
如前所述,您不需要这些列中的大部分,尽管如果没有它们,插值公式将变得更加丑陋,并且故障排除将(更加)困难。

插值后,数据中仅保留至少 7 个连续零的块(可能出现在前几行的零除外。)
“间隔”列对数据进行分组。 它从 1 开始,并在以下情况下递增:
- 时间戳变化超过 100,或者
-插值幂从 0 移动到某个非零值
“尖峰后”列是一个标志,在功率从尖峰下降到稳定(ish)状态期间设置。
它被(重新)设置为零:
- 在第 1 行
- 每当间隔列更改时
- 每当出现新的零块时
只要插值幂从高于 90 的某个值移动到小于或等于 90 的某个幂,它就会设置为 1。 根据样本中的数据,这似乎是合理的。

我们现在可以决定忽略哪些数据——“忽略”列的用途。 当以下任一情况为真时,我们将忽略数据:
- 区间内的最大值 < 400。 (这就是“背景噪音”)。 同样,基于提供的数据。
- 插值功率移动到 100 以上,尖峰后。 (表明我们已经进入“背景噪音”,而不是逐渐下降到 0)
- 插值功率为 0
“Final Power”与“Interpolated Power”相同,但在“Ignore”== 1 时设置为缺失。
“间隔时间”计算相对于间隔的时间(即,从 0 开始并从那里递增)。 就像“最终力量”一样,只要“忽略”== 1,它就会被设置为缺失。
我没有禁用公式评估和子集化未忽略的数据,但这可能是下一步。
看看 Dan 接下来如何打破这个将会很有趣;)
干杯,
布雷迪
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序这是目前为止所有的解决方案JMP 公开:
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序这是一个非常好的解决方案。 我把输出贴在JMP公共使用本地数据过滤器查看新数据集、三个训练运行和二进制稳定状态的特征列:
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序@Daniel_Valente ,
这是我对挑战的尝试。
我的方法是识别图案开始和结束的特征,识别整个运行区域(开始和结束之间),然后为每个区域分配一个图案 ID。
这是我使用的列公式的代码:
names default to here(1);
dt = current data table();
// date from time stamp
dt << New Column( "Date[timestamp]",
Numeric,
Nominal,
Format( "yyyy-mm-dd", 19 ),
Input Format( "yyyy-mm-dd" ),
Formula( :timestamp - Time Of Day( :timestamp ) )
);
// rate of change
dt << New Column( "rate of change",
Numeric,
Continuous,
Formula(
Col Moving Average(
Dif(
Col Moving Average(
:power / Col Maximum( :power, :Name( "Date[timestamp]" ) ),
0.6,
5,
5,
Empty(),
:Name( "Date[timestamp]" )
)
)
,
0.95,
5,
5,
Empty(),
:Name( "Date[timestamp]" )
)
)
);
// Start point
dt << New Column( "Start Point",
Numeric,
Continuous,
Formula(
If( Row() == 1,
0,
checkZero = 1 * (:power[Row()] > 0 & :power[Row() - 1] == 0);
foundPeak = 0;
For( k = Row() + 1, k <= N Rows(), k++,
If( :power[k] > Col Quantile( :power, 0.93, :Name( "Date[timestamp]" ) ),
foundPeak = 1;
Break();
);
If( :power[k] == 0, Break() );
);
If( checkZero == 1 & foundPeak == 1,
1,
0
);
)
)
);
// steady state
dt << New Column( "Steady State",
Numeric,
Continuous,
Formula(
ans = 1;
For( k = 0, k <= 10, k++,
ans = Minimum(
ans,
1 * (:rate of change[Row() - k] < 0.003 &
:rate of change[Row() - k] > -0.003)
)
);
ans;
)
);
// Run Region
dt << New Column( "Run Region",
Numeric,
Continuous,
Format( "Best", 12 ),
Formula(
ans = 0;
For( k = Row(), k >= 1, k--,
If( :Start Point[k] == 1,
ans = 1;
Break();
);
If( :Steady State[k] == 0 & :Steady State[k - 1] == 1,
ans = 0;
Break();
);
);
ans;
)
);
// motif ID
dt << New Column( "Motif ID",
Numeric,
Nominal,
Formula(
If(
:Run Region == 1,
Col Cumulative Sum( :Start Point )
)
)
);
// rank timestamp, Motif ID - helps plot each motif
dt << New Column( "Rank[timestamp]",
Numeric,
Continuous,
Formula( Col Rank( :timestamp, :Motif ID ) )
);回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序不错,拜伦——这看起来很不错。 它正在挑选出那两条噪声曲线。 我将在周末收集一些额外的数据,看看它的表现如何。

回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序好的! 这听起来也是一个不错的解决方案。 如果您能找到它,请发布 JSL,我们将尝试一下。
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序唔。 好问题。 在考虑数据问题时探索假设总是好的。 还要询问拥有生成数据的系统的人!
1) 每场比赛只有一次冷却时间热身。 热身并没有那么大的变化。 平均值就像 ~250 +-30 瓦,除非我喝了几杯咖啡......

2) 如你所见,我真的只能保持最高速度 6-10 秒,然后功率开始下降。 也许将来它们会更长/更宽。 我们将不得不探索这一点。
3) 不应包括步行期后的零点。 他们是一个重置期。 从一开始它基本上是 0 瓦准备。 冲刺一段时间。 减速至停止。 往回走。 归零为下一个区间的集合。
4)主题中的零是错误数据,应该进行插值。 我没有停下来。 只是传感器掉了。 它们应该被修复,标记为丢失或插入,否则我会看到它会导致后续分析出现一些问题。
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序我更改了简单的列公式方法以包含一个 AI 模型,因此现在该表具有一个非常复杂的公式以及一些更简单的公式。
附加脚本添加列和图形。 该模型将曲线段评分为信号或噪声。
我很想知道它将如何对一个独特的数据集进行评分。

回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序我在计算热冲击炉的温度循环的工作中做了一些类似的事情。 我认为在那种情况下,我使用了几个不同的公式列来完成它,我将不得不查看工作中的代码,看看是否可以针对这种情况对其进行修改。
如果我没记错的话,我的想法是检测循环的“起点”(在这种情况下为 0 次幂并获取这些值的最后一个值),然后检查是否有足够的值超过某个阈值(这里可能超过 5 个值) 350),然后检查循环结束的位置(平坦部分,在 80-100 之间有 ~20 个值)。 然后有一些运行变量(motid_id、cycle_ongoing 等),我会根据需要增加和重置这些变量。
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序假设很重要。 您愿意对数据做出什么假设? 这就是您的问题所在。
1) 有多少冷却时间/热身时间是可能的? 你知道这些的平均功率吗? 它有变化吗?
2) Motifs 的长度可能不同……但是,无论是什么,它们的长度总是相似的吗? 到目前为止,在示例数据中,情况一直如此……未来还会如此吗?
3) 零呢? 它们是否应该作为主题的主要部分包括在内? 全部,一定数量的,还是一个都没有?
4) 出现在 mid-motif 中的零怎么办? 这些合法吗? 非零点中间主题怎么样? 我们如何判断这些是否合法? 我们应该尝试吗?
等等等等等等
干杯,
布雷迪
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序这是与连接的第三个文件@Byron_JMP的剧本。 看起来发生了同样的事情。 我们正在标记要排除的部分区域作为主题。 好消息是它似乎在同一个地方失败或使用相同的失败模式。 您可以在循环组 9 和 18 以及样本/功效图中看到它。


回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序哦! 好的@Byron_JMP . 我喜欢公式列解决方案。 只是试图扩展它。 它以不同的方式失败@brady_brady的解决方案。
Motif 9 看起来有点可疑……

回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序呃哦! 当我试图扩展它时出现问题。 我们现在有两天的数据。 运行 Brady 的脚本,希望能识别出 16 个 Motifs。
我现在有新数据进来并运行脚本@brady_brady写了。 正确识别了 16 个图案。 但是现在第8个有些奇怪。 看起来算法被新数据搞糊涂了。 我确实有时间戳,也许这有助于制作这个规模?


去试试@Byron_JMP的解决方案。
回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序@brady_brady这是解决问题的一种非常巧妙的方法。
我用三个简单的列公式做到了。

new column("time point", formula(If( Row() == 1,
1,
If( :power == 0 & Lag( :time point, 1 ) <= 50,
Lag( :time point, 1 ) + 1,
If( :power == 0,
1,
Lag( :time point, 1 ) + 1
)
)
)));
new column("segments", formula(
If(
Row() == 1, 0,
:time point == 1, Lag( :segments, 1 ) + 1,
Lag( :segments, 1 )
)));
new column("cycle", formula(
If( :time point > 70 | :segments == 0,
.,
:segments
)));回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序不错,布雷迪! 做得好。 有趣的是,也存在一些数据质量问题,我可以拟合一个峰值模型来解决这个问题。 也许那些可以被标记为缺失,因为它们不是真正的 0,或插值?回复:JMP 工作流程挑战 1:从连续功率数据中提取和识别基序令人惊讶的是,您可以使用单个公式列获得多远:
dt << new column("Motif ID", nominal, formula(Col Cumulative Sum(
Row() > 6 & Summation( i = 1, 6, Abs( Lag( :power, i ) ) ) == 0 & :power != 0
)));
然而,还有一些清理工作要做……这种方法创建了一个“0”组 ID,这是前导数据,并且不会切断我们想要的最后一个主题。 求和中的“6”是因为如果在功率变化之前遇到至少 6 个连续的零,我将指定一个新组。
当然还有许多其他更复杂的方法可以做到这一点,包括与背景地板的比较、攻击/衰减等……但如果像这样的简单方法有效,那就太好了。
下面的脚本结合了这个想法,但也清理了数据集。 理想情况下,最后几行中的值将被参数化。
names default to here(1);
dt = current data table();
dtSub = dt << subset(selected rows(0), selected columns(0));
//create ID column
dtSub << new column("Motif ID", nominal, formula(Col Cumulative Sum(
Row() > 6 & Summation( i = 1, 6, Abs( Lag( :power, i ) ) ) == 0 & :power != 0
)));
dtSub:MotifID << delete formula;
//the next 2 loops truncate the dataset, assuming it ends like the sample dataset ended.
for(i = nrow(dtSub), abs(dtSub:power[i] - dtSub:power[i-1]) < 20, i--,
dtSub << Delete Rows(i)
);
for(i = nrow(dtSub), abs(dtSub:power[i] - dtSub:power[i-1]) > 5, i--,
dtSub << Delete Rows(i)
);
//delete ID 0 group
dtSub << select where(:Motif ID == 0) << delete rows;
//delete 0 power rows, but not when they lie within a group
i = nrow(dtSub)-1;
while (i > 1,
if(dtSub:Motif ID[i] < dtSub:MotifID[i+1] & dtSub:power[i] == 0,
while( dtSub:power[i] == 0,
dtSub << delete rows(i--);
)
);
i--
);

JMP 工作流程挑战 1:从连续功率数据中提取和识别基序
调用 JMP 工作流程和 JSL 向导。 这是周四给你的挑战。 随附的数据集是我的跑步功率计的功率数据集。 我们想要做的是识别并正确标记在本文所附的约 1000 点信号数据集中出现的 8 个基序,创建一个包含函数 ID 的新列,并以可用于自动标记的方式编写它遵循同一信封的未来数据。
该数据集和未来的数据收集假设是:
- 在主题开始之前,功率为 0。
- 主题前后有一个非零的时间段(热身和热身)。 这不是被测系统的一部分,不应被标记。
- 每个主题都有起音、延音、衰减和释放。 每个主题的功能形状都是相同的。 它在一段采样时间内从 0 变化到某个峰值,然后下降到基线本底噪声(步行功率)。 当电源再次读取 0 时,主题 ID 结束。
- 未来将有未知数量的功能需要识别。 数据集中有8个。 解决方案必须对此保持不变。
- 函数的总长度以及峰值可能会有所不同。
我们正在左侧边缘工作分析工作流程今天(如下图绿色所示)。 数据来自文件 (.csv),最终当我们自动化此工作流程时,会生成一个文件夹n .csv 文件。 正在使用数据访问,今天的任务是对此示例文件执行数据混合和清理任务,以便为将来将工作流程扩展到其他分析功能做好准备。

在评论中留下您的解决方案。
数据混合和清理