JMP 工作流程挑戰 1:從連續功率數據中提取和識別基序
調用 JMP 工作流和 JSL 嚮導。 這是給你的周四挑戰。 附件數據集是我跑步功率計的功率數據集。 我們想要做的是識別並正確標記在這篇文章所附的 ~1000 點信號數據集中出現的 8 個基序,創建一個具有函數 ID 的新列,並以可用於自動標記的方式編寫它遵循相同信封的未來數據。
該數據集和未來的數據收集假設是:
- 在主題開始之前,功率為 0。
- 在非零圖案之前和之後有一段時期(熱身和熱身)。 這不是被測系統的一部分,不應標記。
- 每個主題都有起音、延音、衰減和釋放。 功能的形狀因主題而異。 它在一段時間內從 0 變為某個峰值,然後逐漸下降到基線本底噪聲(步行功率)。 當電源再次讀取 0 時,圖案 ID 結束。
- 將來會有未知數量的功能需要識別。 數據集中有 8 個。 解決方案需要對此保持不變。
- 函數的總長度以及峰值可以變化。
我們在左邊工作分析工作流程今天(下面以綠色顯示)。 數據來自一個文件 (.csv),最終當我們自動化這個工作流時,一個文件夾n .csv 文件。 正在使用數據訪問,今天的任務是在此示例文件上執行數據混合和清理任務,以便為將來將工作流擴展到其他分析功能準備好數據。

在評論中留下您的解決方案。
腳本回复: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--
);
