JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen Leistungsdaten
Aufrufen von JMP-Workflow- und JSL-Wizards. Hier ist eine Donnerstags-Challenge für dich. Der beigefügte Datensatz ist ein Satz von Leistungsdaten meines Laufleistungsmessers. Was wir tun möchten, ist die 8 Motive zu identifizieren und korrekt zu markieren, die während dieses ~1000-Punkt-Signaldatensatzes auftreten, der diesem Beitrag beigefügt ist, eine neue Spalte mit der Funktions-ID zu erstellen und sie so zu schreiben, dass sie zur Automatisierung des Tagging verwendet werden kann von zukünftigen Daten, die demselben Envelope folgen.
Die Datenerhebungsannahmen für diesen Datensatz und die Zukunft sind:
- Vor dem Start eines Motivs ist die Leistung 0.
- Es gibt eine Periode vor und nach den Motiven, die nicht Null ist (Aufwärmen und Aufwärmen). Dies ist nicht Teil des zu testenden Systems und sollte nicht gekennzeichnet werden.
- Jedes Motiv hat Attack, Sustain, Decay und Release. Die Form der Funktion ist von Motiv zu Motiv identisch. Es geht über einen Zeitraum von Samples von 0 bis zu einem gewissen Peak und fällt dann auf das Grundrauschen (Gehleistung) ab. Die Motiv-ID ist vorbei, wenn die Leistung wieder 0 anzeigt.
- Es wird in Zukunft eine unbekannte Anzahl von Funktionen zu identifizieren geben. Es gibt 8 im Datensatz. Die Lösung muss dazu invariant sein.
- Die Gesamtlänge der Funktionen sowie der Peak können variieren.
Wir arbeiten am linken Rand der analytischer Arbeitsablauf heute (unten grün dargestellt). Die Daten stammen aus einer Datei (.csv) und schließlich, wenn wir diesen Workflow automatisieren, aus einem Ordner N .csv-Dateien. Der Datenzugriff wird verwendet, und die Aufgabe besteht heute darin, die Datenverschmelzungs- und Bereinigungsaufgaben für diese Beispieldatei auszuführen, damit die Daten bereit sind, um den Workflow in Zukunft auf andere Analysefunktionen zu erweitern.

Hinterlasse deine Lösungen in den Kommentaren.
SkripterstellungBetreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenFreut mich zu hören, dass das Add-In auf dem Mac funktioniert.
Sie haben Recht, ich habe vergessen, den Filter für "Sprint" wieder hinzuzufügen, nachdem ich das Add-In so generisch wie möglich gemacht hatte.
Der folgende Code:
- verwendet das Add-In, um aktuelle Daten von Strava zu erhalten
- filtert Zeilen so, dass sie nur „Sprint“-Namen enthalten
- filtert die Spalten so, dass sie mit dem übereinstimmen, was im Datensatz „2022_03_28 Strava Sprinting Training Data Set 5 Runs“ gepostet wurde
- identifiziert Motive
- fügt der Datentabelle ein Skript hinzu, um die Ergebnisse grafisch darzustellen
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") );Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenEs funktioniert auf dem Mac, aber wenn Sie das Aktualisierungstoken falsch eingeben, wird das Add-In nicht geladen und Sie müssen es deinstallieren und neu installieren. Zieht das alle neueren Aktivitäten? Es könnte gut sein, eine Suchzeichenfolge zu haben, um nur diejenigen mit dem Titel von Interesse (z. B. "Sprint") abzurufen.
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenIch kann es nicht auf einem Mac testen, daher war meine Standardauswahl nur Windows.Im Anhang ist ein Add-In, das sowohl Windows als auch Mac unterstützt. Mir ist kein Grund bekannt, warum es nicht funktionieren sollte, aber es ist ungetestet.
Bitte lassen Sie mich wissen, ob es erfolgreich auf einem Mac läuft.
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenGibt es einen Grund, warum dies unter macOS nicht funktionieren würde? Ich habe gerade versucht, es zu laden, und Sie haben es möglicherweise nur für Windows erstellt.
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenIch habe über meine anfängliche Herangehensweise an diese Herausforderung nachgedacht und dachte, ich könnte es besser machen, also habe ich einen zweiten Versuch unternommen.
Dieses Mal habe ich mehrere Zielformen definiert und durchsuche dann die Daten nach Regionen, in denen die Formen einigermaßen gut übereinstimmen. Meine definierten Zielformen sind:

Dieses Verfahren erfordert weniger Abstimmungsparameter und ermöglicht mehr Flexibilität, indem zusätzliche Zielformen einfach hinzugefügt werden können.
Ich habe eine Spalte (Watt modifiziert) hinzugefügt, die eine lineare Hochrechnung durchführt, wenn der Wattwert während eines Laufs ausfällt.
Ich habe auch eine Spalte (Row State Formula) hinzugefügt, die automatisch Datenpunkte ausblendet und ausschließt, die keiner Motiv-ID zugeordnet sind.
Meiner Meinung nach ist dies eine Verbesserung gegenüber meiner ursprünglichen Lösung der Herausforderung.
Diese Methode identifiziert 41 Läufe aus dem Datensatz „2022_03_28 Strava Sprinting Training Data Set 5 Runs.jmp“.

Der JSL-Code zur Verarbeitung der Strava-Daten lautet:
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 ) )
);
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenIch freue mich zu hören, dass die Watt für Sie über die API eingehen.
Ich habe ein paar weitere Aktualisierungen am Strava-API-Code vorgenommen, der das automatische Abrufen aktualisierter Zugriffscodes handhabt, und dann habe ich das in ein generisches Add-In zusammengefasst, das ich angehängt habe.
Wenn Sie das Add-In zum ersten Mal ausführen, werden Sie aufgefordert, Ihre Strava-API-Informationen einzugeben

die in Ihrem Add-In-Ordner gespeichert wird, damit Sie sie nicht erneut eingeben müssen.
Das Add-In gibt eine Datentabelle zurück, die alle verfügbaren Aktivitäten von Strava enthält.

Dieses Add-In sollte den Prozess des Abrufens von Daten von der Strava-API weiter rationalisieren, nicht nur für dieses Projekt, sondern für andere, die ihre Daten auch von Strava erkunden möchten.
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenFantastisch @ngambles . Das funktioniert großartig und danke, dass Sie auch den Link zum Video gepostet haben, in dem beschrieben wird, wie Sie das Zugriffstoken erhalten. Diese Daten kommen herein, wie ich sie sehen möchte. Und außerdem ist dies eine nette Verbesserung gegenüber dem manuellen Exportieren und Kombinieren in einer .csv-Datei. Schafft wirklich die Voraussetzungen für die Automatisierung dieses Datenabrufs und die Bereinigung der Daten, um sie regelmäßig zu analysieren und in JMP Live zu veröffentlichen. Und ja, die Watt kommen für mich über die API aus Ihrem Skript herein.

Ich habe eingeklebt @brady_brady 's Formeln von oben, und es werden immer noch nicht alle Intervalle angezeigt. Allerdings dank @ngambles 's Strava-API-Code haben wir ein weiteres interessantes Feature im Datensatz, das es zu untersuchen und zu verwenden gilt, nämlich die geglättete Geschwindigkeitsspalte. Ich denke, das kann nützlich sein. Sie haben jetzt auch die eindeutigen Aktivitäts-IDs, mit denen Sie als weiteres Feature arbeiten können (da Sie wissen, dass ich bei einer bestimmten Aktivität nur zwischen 6 und 10 Sprints ausführen werde. Ich denke, wir sollten das ML-Modell noch einmal darauf werfen, @Byron_JMP , da wir jetzt über einen größeren Trainingsdatensatz und ein paar zusätzliche Funktionen zum Erstellen des Modells verfügen.
Ich habe den aktualisierten Trainingsdatensatz angehängt.

Und @Jordan_Hiller Ich denke, du solltest es auch versuchen! Sie sind ein Datenbereinigungsprofi.
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenIch habe versucht, Daten direkt von der Strava-API abzurufen.Ich erhalte erfolgreich Messungen für Entfernung, Geschwindigkeit, Position, Höhe und Neigung. Allerdings bekomme ich keine Leistungsmessungen.Ich habe den Verdacht, dass dies an meinem Strava-Kontostand und/oder an meinem fehlenden Leistungsmessgerät liegt. Ich habe Grund zu der Annahme, dass mein Code für Benutzer funktionieren könnte, die Leistungsdaten in ihren Strava-Daten haben, aber ich kann das nicht bestätigen. Wenn diese Lösung nicht richtig ist, kann ich mir nicht vorstellen, dass sie zu weit von dem entfernt ist, was benötigt wird.
ich habe das gefunden Youtube-Video sehr informativ für den Einstieg in die Strava-API. Es zeigt deutlich, wie Sie Ihr Zugriffstoken von der Strava-API erhalten, die erforderlich ist, bevor der JSL-Code Informationen von Strava erhält. Ersetzen Sie im folgenden Code your_access_token_here durch Ihr Zugriffstoken, das Sie vor der Ausführung von der Strava-API erhalten.
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 );Der Code:
- durchsucht alle Strava-Aktivitäten und wählt nur diejenigen mit „Sprint“ im Namen aus (Groß-/Kleinschreibung wird nicht beachtet)
- versucht, viele Messungen zu erhalten, einschließlich Leistung (Watt)
- organisiert die Informationen in einer Datentabelle ähnlich den ursprünglichen Datensätzen, die in dieser Herausforderung bereitgestellt werden
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenIch habe versucht, Daten direkt von der Strava-API abzurufen. Ich erhalte erfolgreich mehrere Messungen, darunter Entfernung, Geschwindigkeit, Länge, Breite, Höhe und Steigung. Allerdings bekomme ich keinen Strom. Ich vermute, dass dies an meinem Strava-Konto und/oder meiner fehlenden Ausrüstung zur Leistungsmessung liegen könnte. Diese Lösung kann für jemanden mit Energieinformationen in seinem Strava-Konto erforderlich sein oder auch nicht, aber wenn sie nicht korrekt ist, kann ich mir nicht vorstellen, dass ich zu weit weg bin.
Um mit der Strava-API zu beginnen, dies Youtube Das Video war sehr hilfreich und zeigt deutlich die Schritte zum Abrufen Ihres Zugriffstokens, das erforderlich ist, bevor dieser Code Daten von Strava erhält. Ersetzen Sie im folgenden Code your_access_token_here durch Ihr tatsächliches Zugriffstoken, sobald Sie es haben.
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 );
Der Code:
- schließt nur Daten für Aktivitäten ein, die „Sprint“ im Namen enthalten, Groß- und Kleinschreibung wird nicht beachtet
- versucht, neben vielen anderen Messungen, einschließlich Entfernung, Geschwindigkeit, Position, Höhe usw., Leistung (Watt) zu erhalten.
- ordnet die Daten in einem Format an, das den ursprünglichen Datentabellen ähnelt, die mit dieser Herausforderung präsentiert werden
Ich hoffe, dass dieser Code für einen Benutzer funktioniert, der Energieinformationen in seinem Strava-Konto hat.
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenSchön, Brady.
Guter Punkt über einige der ziemlich kurzen Zeiten vom Beginn eines Intervalls im Vergleich zu einem Aussetzer. Die Aussetzer scheinen jedoch maximal 2-3 Sekunden zu betragen. Wir werden sehen, wie sich das hält, wenn diese Woche neue Daten gesammelt werden.
Anbei einige zusätzliche Trainingsdaten. Dies ist ungefähr 1:40 eines sehr stabilen Laufens, aber eher ein Renntempo (fast 100% meiner kritischen Leistung). Es könnte helfen @Byron_JMP , aber mit seinem ML-Modell. Vielleicht fügen Sie diesem Trainingsdatensatz eine Funktion hinzu, um ihn näher an den Aufwärm-/Abwärmbereich zu bringen?
Auch wer es angehen möchte, die Daten direkt über die Strava-API mit HTTP Request () zu erhalten; im JMP? Ich weiss @dieter_pisot hat das schon mal gemacht. Ich kann die Workouts mit so etwas wie Sprints taggen, sodass der Datenabruf einfach über die API abgerufen werden kann.

Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenHier ist ein weiterer Ansatz mit Interpolation der Aussetzer, wobei einige (relativ einfache) Formelspalten verwendet werden, von denen viele durch die Verwendung einer komplexeren Interpolationsformel auf Kosten einer einfachen Fehlersuche und des Schreibens von Formeln eliminiert werden könnten. (Siehe beigefügte Tabelle für Formeln und Diagrammskripte)
Die Ergebnisse scheinen mit dem, was wir wollen, zumindest im Beispieldatensatz übereinzustimmen: (rote Punkte werden fallen gelassen)


So funktioniert es, damit Sie beurteilen können, ob die getroffenen Annahmen für Ihre Daten gelten:
Erstens Interpolation der durch "Aussetzer" auftretenden Nullstellen. Lineare Interpolation wurde gewählt; Splines und Quadrate wurden berücksichtigt, aber die Ergebnisse waren für mein Auge unterlegen.
Beachten Sie die Spalten „Cum Zeros Behind“ bis „Interpolated Power“ in den Zeilen 4-6 unten.
„Cum Zeros Behind“ ist ein umfassender Lookback für Nullen.
„Cum Zeros Ahead“ ist ein umfassender Ausblick auf Nullen.
"Nullblock" ist die Anzahl zusammenhängender Nullen im Block.
"Gruppen für Nullen" beginnt bei 1 und erhöht sich, wenn eine neue Gruppe von Nullen auftritt
Interpolation tritt auf, wenn es einen kurzen (weniger als 7) Block von Nullen gibt, der nicht in Zeile 1 beginnt.
****Hinweis: In den Beispieldaten umfasste der längste Interpolationsfall 4 Nullen, während die kürzeste „wahre“ Unterbrechung zwischen den Intervallen 7 Nullen betrug. Dies ist ein ziemlich geringer Spielraum für Fehler und ein möglicherweise erheblicher Nachteil dieses Ansatzes. Wichtig ist, dass die kürzeste „echte“ Pause länger ist als der längste „Aussetzer“.
Wie bereits erwähnt, benötigen Sie die meisten dieser Spalten nicht, obwohl die Formel für die Interpolation ohne sie viel hässlicher wird und die Fehlersuche (viel) schwieriger wird.

Nach der Interpolation verbleiben in den Daten nur noch Blöcke mit mindestens 7 aufeinanderfolgenden Nullen (mit Ausnahme von möglicherweise in den ersten Zeilen auftretenden Nullen).
Die Spalte "Intervall" gruppiert die Daten. Es beginnt bei 1 und erhöht sich, wenn:
-Der Zeitstempel ändert sich um mehr als 100, oder
-interpolierte Leistung bewegt sich von 0 auf einen Wert ungleich Null
Die „Post-Spike“-Spalte ist ein Flag, das während des Herunterfahrens der Leistung von der Spitze in den stabilen (ähnlichen) Zustand gesetzt wird.
Es wird auf Null (zurück)gesetzt:
- in Reihe 1
- immer wenn sich die Intervallspalte ändert
- immer wenn ein neuer Nullblock auftritt
Es wird auf 1 gesetzt, wenn sich die interpolierte Leistung von einem Wert über 90 zu einer Leistung kleiner oder gleich 90 bewegt. Basierend auf den Daten in der Stichprobe erschien dies vernünftig.

Wir können jetzt entscheiden, welche Daten ignoriert werden sollen – der Zweck der Spalte „Ignorieren“. Wir ignorieren Daten, wenn einer der folgenden Punkte zutrifft:
- Das Maximum über das Intervall ist < 400. (Dies ist das "Hintergrundrauschen"). Wieder basierend auf den bereitgestellten Daten.
- Interpolierte Power bewegt sich über 100, Post-Spike. (Dies zeigt an, dass wir uns in "Hintergrundgeräusche" bewegt haben, anstatt auf 0 herunterzufahren)
- Interpolierte Leistung ist 0
„Final Power“ ist dasselbe wie „Interpolated Power“, wird aber auf „Missing“ gesetzt, wenn „Ignore“ == 1.
"Intervallzeit" berechnet die Zeit relativ zum Intervall (dh beginnt bei 0 und erhöht sich von dort). Wie „Final Power“ wird es immer dann auf „Missing“ gesetzt, wenn „Ignore“ == 1 ist.
Ich habe Formelauswertungen nicht deaktiviert und die nicht ignorierten Daten untergliedert, aber dies ist wahrscheinlich ein nächster Schritt.
Es wird interessant sein zu sehen, wie Dan das als nächstes bricht 
Beifall,
Brady
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenHier sind alle bisherigen Lösungen JMP-Öffentlichkeit:
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenDas ist eine wirklich schöne Lösung. Ich habe die Ausgabe auf gepostet JMP-Öffentlichkeit mit einem lokalen Datenfilter, um sich den neuen Datensatz, die drei Trainingsläufe und Ihre Feature-Spalte des binären stationären Zustands anzusehen:
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen Leistungsdaten@Daniel_Valente ,
Hier ist mein Versuch der Herausforderung.
Mein Ansatz besteht darin, Merkmale am Anfang und am Ende des Motivs zu identifizieren, die gesamte Laufregion (zwischen Anfang und Ende) zu identifizieren und dann jedem eine Motiv-ID zuzuweisen.
Hier ist mein Code für die Spaltenformeln, die ich verwendet habe:
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 ) )
);Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenNett, Byron – das sieht ziemlich gut aus. Es wählt diese beiden Rauschkurven aus. Ich werde über das Wochenende einige zusätzliche Daten sammeln und sehen, wie es fair ist.

Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenHübsch! Das klingt auch nach einer guten Lösung. Posten Sie das JSL, wenn Sie es ausgraben können, und wir werden es versuchen.
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenHmm. Gute Fragen. Es ist immer gut, Annahmen zu untersuchen, wenn man über ein Datenproblem nachdenkt. Und fragen Sie auch die Leute, denen das System gehört, das die Daten generiert!
1) Nur ein Cooldown-Warmup pro Trainingseinheit. Das Aufwärmen variiert nicht wirklich sehr. Der Durchschnitt liegt bei ~ 250 + -30 Watt, es sei denn, ich habe ein paar Tassen Kaffee getrunken ...

2) Wie Sie sehen können, kann ich meine Höchstgeschwindigkeit wirklich nur 6-10 Sekunden lang halten, und dann beginnt die Leistung zu sinken. Vielleicht werden sie in Zukunft länger/breiter. Das müssen wir erforschen.
3) Nullen nach der Gehzeit sollten nicht enthalten sein. Sie sind eine Reset-Periode. Es ist weitgehend 0 Watt Vorbereitung von Anfang an. Sprinte für eine gewisse Zeit. Langsam bis zum Stillstand. Zurückgehen. Zurück zu Null für den Satz des nächsten Intervalls.
4) Nullen während des Motivs sind schlechte Daten und sollten interpoliert werden. Ich habe nicht aufgehört. Nur der Sensor ist ausgefallen. Sie sollten behoben, als fehlend markiert oder durch interpoliert werden, da ich sonst sehen könnte, dass dies einige Probleme bei der anschließenden Analyse verursacht.
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenIch habe den Ansatz der einfachen Spaltenformel dahingehend geändert, dass er ein KI-Modell enthält, sodass die Tabelle jetzt eine fantastisch komplexe Formel zusammen mit einigen einfacheren enthält.
Das angehängte Skript fügt Spalten und ein Diagramm hinzu. Das Modell bewertet die Kurvensegmente als Signal oder Rauschen.
Ich bin gespannt, wie es einen einzigartigen Datensatz bewertet.

Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenIch habe etwas Ähnliches bei der Arbeit gemacht, wo ich Temperaturzyklen für Thermoschocköfen gezählt habe. Ich denke, in diesem Fall habe ich es mit ein paar verschiedenen Formelspalten gemacht, ich muss mir den Code bei der Arbeit ansehen und sehen, ob er für diesen Fall geändert werden könnte.
Wenn ich mich richtig erinnere, war meine Idee, den "Startpunkt" des Zyklus zu erkennen (in diesem Fall 0 Leistung und den letzten Wert davon zu erhalten), und dann zu überprüfen, ob genügend Werte vorhanden sind, die über einem Schwellenwert liegen (hier könnten es 5 Werte sein). 350) und danach prüfen, wo der Zyklus endet (flacher Teil mit ~20 Werten zwischen 80-100). Und dann habe ich einige laufende Variablen (motid_id, cycle_ongoing, ...), die ich nach Bedarf erhöhen und zurücksetzen würde.
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenAnnahmen sind wichtig. Was sind Sie bereit, in Bezug auf die Daten anzunehmen? Dort werden Ihre Probleme liegen.
1) Wie viele Cooldowns/Warmups sind möglich? Kennen Sie die mittlere Leistung für diese? Variiert es?
2) Motive können unterschiedlich lang sein... werden sie aber immer gleich lang sein, was auch immer das ist? In den bisherigen Beispieldaten war dies der Fall ... wird dies auch in Zukunft so bleiben?
3) Was ist mit Nullen? Sollen sie als führender Teil des Motivs aufgenommen werden oder nicht? Alle, eine gewisse Anzahl, keiner?
4) Was ist mit Nullen, die mitten im Motiv vorkommen? Sind diese legitim? Was ist mit Nicht-Null-Punkten in der Mitte des Motivs? Wie können wir feststellen, ob diese legitim sind? Sollen wir es versuchen?
usw. usw. usw
Beifall,
Brady
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenHier ist es mit der dritten Datei verkettet @Byron_JMP 's Skript. Sieht so aus, als ob das gleiche passiert. Wir markieren einen Teil des Bereichs, der als Motiv ausgeschlossen werden soll. Das Gute ist, dass es an derselben Stelle oder mit demselben Fehlermodus zu versagen scheint. Sie können es in den Zyklusgruppen 9 und 18 und auch im Sample/Power-Plot sehen.


Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenOooh! Hübsch @Byron_JMP . Ich mag die Formelspaltenlösung. Habe gerade versucht es zu skalieren. Es scheitert auf andere Weise als @brady_brady s Lösung.
Motiv 9 sieht etwas suspekt aus...

Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenOh oh! Problem, wenn ich versuche, dies zu skalieren. Wir haben jetzt zwei Tage Daten. Bradys Drehbuch lief, um hoffentlich 16 Motive zu identifizieren.
Ich habe jetzt neue Daten, die hereinkommen, und habe das Skript ausgeführt @brady_brady schrieb. 16 Motive richtig identifiziert. Aber es gibt jetzt eine Kuriosität in der 8. Sieht so aus, als wäre der Algorithmus durch die neuen Daten verwirrt. Ich habe den Zeitstempel, vielleicht könnte das bei der Erstellung dieser Skala helfen?


Ab zum Probieren @Byron_JMP s Lösung.
Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen Leistungsdaten@brady_brady Das ist eine ziemlich raffinierte Methode, um das Problem zu lösen.
Ich habe es mit drei einfachen Spaltenformeln gemacht.

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
)));Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenSchön, Brady! Gute Arbeit. Es ist interessant, dass es auch einige Datenqualitätsprobleme gibt, ich kann ein Spitzenmodell anpassen, um das auszugleichen. Vielleicht könnten diese als fehlend markiert werden, weil sie nicht wirklich 0 sind, oder interpoliert?Betreff: JMP-Workflow-Herausforderung 1: Motivextraktion und -identifikation aus kontinuierlichen LeistungsdatenEs ist überraschend, wie weit man mit einer einzigen Formelspalte kommt:
dt << new column("Motif ID", nominal, formula(Col Cumulative Sum(
Row() > 6 & Summation( i = 1, 6, Abs( Lag( :power, i ) ) ) == 0 & :power != 0
)));
Es muss jedoch noch etwas aufgeräumt werden ... dieser Ansatz erstellt eine "0"-Gruppen-ID, die die führenden Daten sind, und schneidet das letzte Motiv nicht an der gewünschten Stelle ab. Die "6" in der Summierung liegt daran, dass ich eine neue Gruppe bezeichne, wenn mindestens 6 aufeinanderfolgende Nullen vor einer Potenzänderung angetroffen werden.
Es gibt natürlich viele andere Möglichkeiten, dies zu tun, die ausgefeilter sind und Vergleiche mit den Hintergrundböden, Attacks / Decays usw. beinhalten, aber wenn ein einfacher Ansatz wie dieser funktioniert, großartig.
Das folgende Skript enthält diese Idee, bereinigt aber auch den Datensatz. Idealerweise werden die Werte in den letzten paar Zeilen parametrisiert.
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--
);
