Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueHeureux d'apprendre que le complément fonctionne sur Mac.
Vous avez raison, j'ai oublié d'ajouter le filtre pour "sprint" après avoir rendu le complément aussi générique que possible.
Le code suivant :
- utilise le complément pour obtenir de nouvelles données de Strava
- filtre les lignes pour n'inclure que les noms de "sprint"
- filtre les colonnes pour qu'elles correspondent à ce qui a été publié dans l'ensemble de données "2022_03_28 Strava Sprinting Training Data Set 5 Runs"
- identifie les motifs
- ajoute un script à la table de données pour tracer les résultats
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") );Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueCela fonctionne sur Mac, cependant, si vous entrez le jeton d'actualisation de manière incorrecte, le complément refusera de se charger et vous devrez le désinstaller et le réinstaller. Est-ce que cela tire toutes les activités récentes ? Il serait peut-être bon d'avoir une chaîne de recherche pour n'extraire que ceux qui portent le titre qui vous intéresse (c'est-à-dire "sprint").
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueJe ne peux pas le tester sur un Mac, donc mon choix par défaut était Windows uniquement.Attaché est un complément qui permet à la fois Windows et Mac. Je ne connais pas de raison pour laquelle cela ne devrait pas fonctionner, mais cela n'a pas été testé.
Veuillez me faire savoir s'il fonctionne correctement sur un Mac.
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueY a-t-il une raison pour laquelle cela ne fonctionnerait pas sur macOS ? Je viens d'essayer de le charger et vous l'avez peut-être créé pour Windows uniquement.
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueJ'ai réfléchi à mon approche initiale de ce défi et j'ai pensé que je pouvais mieux le faire, alors j'ai fait une deuxième tentative.
Cette fois, j'ai défini plusieurs formes cibles, puis j'ai parcouru les données à la recherche de régions où les formes correspondent raisonnablement bien. Mes formes cibles définies sont :

Cette méthode nécessite moins de paramètres de réglage et offre plus de flexibilité en permettant d'ajouter facilement des formes cibles supplémentaires.
J'ai ajouté une colonne (watts modifiés) qui effectue une extrapolation linéaire lorsque la valeur en watts chute pendant une course.
J'ai également ajouté une colonne (Row State Formula) qui masque et exclut automatiquement les points de données qui ne sont pas associés à un ID de motif.
À mon avis, c'est une amélioration par rapport à ma solution initiale au défi.
Cette méthode identifie 41 runs à partir du jeu de données "2022_03_28 Strava Sprinting Training Data Set 5 Runs.jmp".

Le code JSL pour traiter les données Strava est :
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 ) )
);
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueJe suis heureux d'apprendre que les watts arrivent pour vous via l'API.
J'ai apporté quelques mises à jour supplémentaires au code de l'API Strava qui gère automatiquement l'obtention de codes d'accès actualisés, puis j'ai intégré cela dans un complément générique que j'ai joint.
La première fois que vous exécutez le complément, vous serez invité à entrer vos informations d'API Strava

qui est enregistré dans votre dossier de compléments afin que vous n'ayez pas à le saisir à nouveau.
Le complément renvoie une table de données contenant toutes les activités disponibles de Strava.

Ce complément devrait rationaliser davantage le processus d'obtention de données à partir de l'API Strava, non seulement pour ce projet, mais pour d'autres qui souhaitent également explorer leurs données à partir de Strava.
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueFantastique @ngambles . Cela fonctionne très bien et merci d'avoir également publié le lien vers la vidéo, qui décrit comment obtenir le jeton d'accès. Ces données arrivent comme j'aimerais les voir. Et en plus, c'est une belle amélioration par rapport à leur exportation manuelle et leur combinaison dans un fichier .csv. ouvre vraiment la voie à l'automatisation de cette extraction de données et au nettoyage des données pour qu'elles soient analysées et publiées régulièrement sur JMP Live. Et oui, les watts arrivent pour moi via l'API de votre script.

j'ai collé @brady_brady les formules d'en haut, et il n'obtient toujours pas tous les intervalles. Cependant, grâce à @ngambles Dans le code de l'API Strava, nous avons une autre fonctionnalité intéressante dans l'ensemble de données à explorer et à utiliser, qui est la colonne de vitesse lissée. Je pense que cela peut être utile. Vous avez également maintenant les identifiants d'activité uniques avec lesquels travailler comme une autre fonctionnalité (sachant que je n'exécuterai qu'entre 6 et 10 sprints dans une activité donnée. Je pense que nous devrions relancer le modèle ML, @Byron_JMP , maintenant que nous disposons d'un ensemble de données d'entraînement plus volumineux et de quelques fonctionnalités supplémentaires pour créer le modèle.
J'ai joint l'ensemble de données d'entraînement mis à jour.

Et @Jordan_Hiller Je pense que tu devrais essayer aussi ! Vous êtes un pro du nettoyage de données.
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueJ'ai tenté d'obtenir des données directement à partir de l'API Strava.J'obtiens avec succès des mesures de distance, de vitesse, de position, d'altitude et de pente. Cependant, je n'obtiens pas de mesures de puissance.Je soupçonne que cela est dû au niveau de mon compte Strava et/ou à mon manque d'équipement de mesure de puissance. J'ai des raisons de croire que mon code pourrait fonctionner pour les utilisateurs qui ont des données de puissance dans leurs données Strava, mais je ne suis pas en mesure de confirmer. Si cette solution n'est pas correcte, je ne peux pas imaginer qu'elle soit trop éloignée de ce qui est nécessaire.
j'ai trouvé ça vidéo Youtube très instructif pour démarrer avec l'API Strava. Il montre clairement comment obtenir votre jeton d'accès à partir de l'API Strava, qui est nécessaire avant que le code JSL n'obtienne des informations de Strava. Dans le code ci-dessous, remplacez your_access_token_here par votre jeton d'accès que vous obtenez de l'API Strava avant l'exécution.
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 );Le code:
- parcourt toutes les activités Strava et sélectionne uniquement celles dont le nom contient "sprint" (non sensible à la casse)
- essaie d'obtenir de nombreuses mesures, y compris la puissance (watts)
- organise les informations dans un tableau de données similaire aux ensembles de données originaux fournis dans ce défi
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueJ'ai pris une photo pour obtenir des données directement à partir de l'API Strava. J'obtiens avec succès plusieurs mesures, notamment la distance, la vitesse, la longitude, la latitude, l'altitude et la pente. Cependant je n'ai pas de courant. Je soupçonne que cela peut être dû à mon compte Strava et/ou à mon manque d'équipement pour mesurer la puissance. Cette solution peut ou non être ce qui est nécessaire pour quelqu'un avec des informations de puissance dans son compte Strava, mais si ce n'est pas correct, je ne peux pas imaginer que je suis trop loin.
Pour démarrer avec l'API Strava, ceci Youtube La vidéo a été très utile et montre clairement les étapes pour obtenir votre jeton d'accès, qui est requis avant que ce code n'obtienne des données de Strava. Dans le code ci-dessous, remplacez your_access_token_here par votre jeton d'accès réel, une fois que vous l'avez.
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 );
Le code:
- inclut uniquement les données pour les activités qui contiennent "sprint", non sensible à la casse, dans le nom
- essaie d'obtenir la puissance (watts), parmi de nombreuses autres mesures, y compris la distance, la vitesse, la position, l'altitude, etc...
- organise les données dans un format similaire aux tableaux de données originaux présentés avec ce défi
J'espère que ce code fonctionnera pour un utilisateur qui dispose d'informations sur l'alimentation dans son compte Strava.
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueBravo, Brady.
Bon point sur certains des temps assez courts entre le début d'un intervalle et un abandon. Cependant, les abandons semblent être de 2 à 3 secondes maximum. Nous verrons comment cela se tient avec de nouvelles données collectées cette semaine.
Ci-joint quelques données de formation supplémentaires. Il s'agit d'environ 1h40 de fonctionnement en régime très stable, mais à un rythme plus rapide (près de 100 % de ma puissance critique). Cela pourrait aider @Byron_JMP , cependant, avec son modèle ML. Peut-être ajouter une fonctionnalité à cet ensemble de données d'entraînement pour le rapprocher de la plage d'échauffement/réchauffement ?
Aussi, qui veut s'attaquer à l'obtention des données directement via l'API Strava en utilisant HTTP Request (); dans JMP ? Je sais @dieter_pisot a déjà fait cela. Je peux étiqueter les séances d'entraînement avec quelque chose comme des sprints afin que l'extraction de données puisse être facilement récupérée via l'API.

Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueVoici une autre approche avec interpolation des abandons, utilisant des colonnes de formules (relativement simples), dont beaucoup pourraient être éliminées en utilisant une formule d'interpolation plus complexe, au détriment d'un dépannage et d'une écriture de formule faciles. (Voir le tableau ci-joint pour les formules et les scripts graphiques)
Les résultats semblent cohérents avec ce que nous voulons, du moins dans l'échantillon de données : (les points rouges seront supprimés)


Voici comment cela fonctionne afin que vous puissiez juger si les hypothèses formulées sont valables pour vos données :
Tout d'abord, l'interpolation des zéros se produisant en raison des "abandons". L'interpolation linéaire a été choisie ; les splines et les quadratiques ont été pris en compte mais les résultats étaient inférieurs à mes yeux.
Notez les colonnes "Cum Zeros Behind" à "Interpolated Power" dans les rangées 4 à 6 ci-dessous.
"Cum Zeros Behind" est un retour en arrière inclusif pour les zéros.
"Cum Zeros Ahead" est une anticipation inclusive des zéros.
"Zero Block" est le nombre de zéros contigus dans le bloc.
"Groups for Zeros" commence à 1 et augmente chaque fois qu'un nouveau groupe de zéros se produit
L'interpolation se produit lorsqu'il y a un bloc court (moins de 7) de zéros, ne commençant pas à la ligne 1.
****Remarque : dans les données d'échantillon, le cas d'interpolation le plus long s'étendait sur 4 zéros, tandis que la "vraie" pause la plus courte entre les intervalles était de 7 zéros. C'est une marge d'erreur assez mince, et c'est peut-être un inconvénient important de cette approche. Il est important que la "véritable" pause la plus courte soit plus longue que la "décroche" la plus longue.
Comme mentionné précédemment, vous n'avez pas besoin de la plupart de ces colonnes, bien que la formule d'interpolation devienne beaucoup plus laide sans elles, et le dépannage sera (beaucoup) plus difficile.

Après l'interpolation, seuls les blocs d'au moins 7 zéros consécutifs restent dans les données (les zéros pouvant apparaître dans les premières lignes à l'exception.)
La colonne "Intervalle" regroupe les données. Il commence à 1 et s'incrémente à chaque fois que :
-l'horodatage change de plus de 100, ou
-la puissance interpolée passe de 0 à une valeur non nulle
La colonne "Post-pic" est un indicateur qui s'active pendant la descente de la puissance du pic à l'état stable (plutôt).
Il est (re)mis à zéro :
- au rang 1
- chaque fois que la colonne d'intervalle change
- chaque fois qu'un nouveau bloc de zéros se produit
Il est défini sur 1 chaque fois que la puissance interpolée passe d'une valeur supérieure à 90 à une puissance inférieure ou égale à 90. D'après les données de l'échantillon, cela semblait raisonnable.

Nous pouvons maintenant décider quelles données ignorer - le but de la colonne "Ignorer". Nous ignorons les données lorsque l'une des conditions suivantes est vraie :
- Le maximum sur l'intervalle est < 400. (C'est le "bruit de fond"). Encore une fois, sur la base des données fournies.
- La puissance interpolée se déplace au-dessus de 100, après le pic. (Indiquant que nous sommes passés au "bruit de fond", plutôt que de descendre vers 0)
- La puissance interpolée est 0
"Puissance finale" est identique à "Puissance interpolée", mais définie sur manquante lorsque "Ignorer" == 1.
"Interval Time" calcule le temps relatif à l'intervalle (c'est-à-dire, commence à 0 et incrémente à partir de là). Comme "Final Power", il est défini sur manquant chaque fois que "Ignore" == 1.
Je n'ai pas désactivé les évaluations de formule ni sous-ensemble les données non ignorées, mais il s'agit probablement d'une prochaine étape.
Il sera intéressant de voir comment Dan casse cela ensuite 
Acclamations,
brady
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueVoici toutes les solutions jusqu'à présent Publique JMP :
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueC'est une très belle solution. J'ai posté la sortie sur Publique JMP avec un filtre de données local pour examiner le nouvel ensemble de données, les trois exécutions d'entraînement et votre colonne de caractéristiques de l'état stable binaire :
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continue@Daniel_Valente ,
Voici ma tentative de défi.
Mon approche consiste à identifier les caractéristiques au début et à la fin du motif, à identifier toute la région de course (entre le début et la fin), puis à attribuer un ID de motif à chacun.
Voici mon code pour les formules de colonne que j'ai utilisées :
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 ) )
);Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueBien, Byron -- ça a l'air plutôt bien. Il sélectionne ces deux courbes de bruit. Je vais collecter des données supplémentaires au cours du week-end et voir comment cela se passe.

Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueBon! Cela semble être une bonne solution aussi. Postez le JSL si vous pouvez le déterrer et nous l'essayerons.
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueHmm. Bonnes questions. Il est toujours bon d'explorer des hypothèses lorsque l'on pense à un problème de données. Et demandez également aux personnes qui possèdent le système qui génère les données !
1) Un seul échauffement de récupération par session. L'échauffement ne varie pas vraiment beaucoup. La moyenne est d'environ 250 + -30 watts à moins que j'aie bu quelques tasses de café...

2) Comme vous pouvez le voir, je ne peux vraiment maintenir ma vitesse de pointe que pendant 6 à 10 secondes, puis la puissance commence à baisser. Peut-être qu'à l'avenir, ils seront plus longs / plus larges. Nous devrons explorer cela.
3) Les zéros après la période de marche ne doivent pas être inclus. Ils sont une période de réinitialisation. C'est en grande partie 0 watts de préparation dès le départ. Sprintez pendant un certain temps. Ralentissez jusqu'à l'arrêt. Reculez. Retour à zéro pour l'ensemble de l'intervalle suivant.
4) Les zéros pendant le motif sont de mauvaises données et doivent être interpolés. Je n'ai pas arrêté. C'est juste que le capteur a lâché. Ils doivent être corrigés, marqués comme manquants ou interpolés, sinon je pourrais voir que cela cause des problèmes avec l'analyse ultérieure.
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueJ'ai changé l'approche de la formule de colonne simple pour inclure un modèle d'IA, donc maintenant la table a une formule incroyablement complexe avec d'autres plus simples.
Le script joint ajoute des colonnes et un graphique. Le modèle note les segments de courbe comme signal ou bruit.
Je suis intéressé de voir comment il marquera un ensemble de données unique.

Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueJ'ai fait quelque chose d'un peu similaire au travail où je comptais les cycles de température pour le four à choc thermique. Je pense que dans ce cas, je l'ai fait avec quelques colonnes de formules différentes, je devrai jeter un œil au code au travail et voir s'il pourrait être modifié pour ce cas.
Si je me souviens bien, mon idée était de détecter le "point de départ" du cycle (puissance 0 dans ce cas et d'obtenir la dernière valeur de celles-ci), puis de vérifier qu'il y a suffisamment de valeurs supérieures à un certain seuil (ici, il pourrait s'agir de 5 valeurs supérieures 350) et après cela, vérifiez où le cycle se termine (partie plate avec ~20 valeurs entre 80 et 100). Et puis avoir quelques variables en cours d'exécution (motid_id, cycle_ongoing, ...) que j'augmenterais et réinitialiserais au besoin.
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueLes hypothèses comptent. Qu'êtes-vous prêt à supposer à propos des données ? C'est là que résideront vos problèmes.
1) combien de temps de recharge/d'échauffement sont possibles ? Connaissez-vous la puissance moyenne de ceux-ci ? Est-ce que ça varie ?
2) Les motifs peuvent varier en longueur... seront-ils cependant toujours de longueur similaire, quoi qu'il en soit ? Dans les données d'échantillon jusqu'à présent, cela a été le cas... cela restera-t-il le cas à l'avenir ?
3) Et les zéros ? Doivent-ils être inclus comme élément principal du motif ou non ? Tous, un certain nombre, aucun ?
4) Qu'en est-il des zéros qui apparaissent au milieu du motif ? Sont-ils légitimes ? Qu'en est-il du mi-motif non nul ? Comment savoir si ceux-ci sont légitimes ? Doit-on essayer ?
etc.etc.etc
Acclamations,
brady
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueLe voici avec le troisième fichier concaténé avec @Byron_JMP le script. On dirait que la même chose se produit. Nous marquons une partie de la zone à exclure en tant que motif. La bonne chose est qu'il semble échouer au même endroit ou en utilisant le même mode de défaillance. Vous pouvez le voir dans les groupes de cycles 9 et 18 ainsi que dans le graphique échantillon/puissance.


Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueOoh ! Bon @Byron_JMP . J'aime la solution de colonne de formule. J'ai juste essayé de le mettre à l'échelle. Il échoue d'une manière différente de @brady_brady la solution.
Le motif 9 semble un peu suspect...

Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueOh oh ! Problème lorsque j'essaie de mettre cela à l'échelle. Nous avons maintenant deux jours de données. Le script de Ran Brady pour identifier, espérons-le, 16 motifs.
J'ai de nouvelles données qui arrivent maintenant et j'ai exécuté le script @brady_brady écrit. 16 motifs correctement identifiés. Mais il y a une bizarrerie dans le 8e maintenant. On dirait que l'algorithme est confus par les nouvelles données. J'ai l'horodatage, peut-être que cela pourrait aider à faire cette échelle ?


C'est parti pour essayer @Byron_JMP la solution.
Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continue@brady_brady C'est une méthode assez astucieuse pour résoudre le problème.
Je l'ai fait avec trois formules de colonne simples.

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
)));Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueBravo Brady ! Bon travail. Il est intéressant qu'il y ait aussi des problèmes de qualité des données, je peux ajuster un modèle de pointe pour lisser cela. Peut-être que ceux-ci pourraient être marqués comme manquants car ils ne sont pas vraiment 0 ou interpolés ?Objet : JMP Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continueIl est surprenant de voir jusqu'où vous pouvez aller avec une seule colonne de formules :
dt << new column("Motif ID", nominal, formula(Col Cumulative Sum(
Row() > 6 & Summation( i = 1, 6, Abs( Lag( :power, i ) ) ) == 0 & :power != 0
)));
Cependant, il reste encore du nettoyage à faire... cette approche crée un identifiant de groupe "0", qui est la donnée principale, et ne coupe pas le dernier motif là où nous le souhaiterions. Le "6" dans la sommation est dû au fait que je désigne un nouveau groupe si au moins 6 zéros consécutifs sont rencontrés avant un changement de puissance.
Il existe bien sûr de nombreuses autres façons de le faire qui sont plus sophistiquées, impliquant des comparaisons avec les étages d'arrière-plan, les attaques/décroissances, etc... mais si une approche simple comme celle-ci fonctionne, tant mieux.
Le script ci-dessous intègre cette idée, mais nettoie également l'ensemble de données. Idéalement, les valeurs des dernières lignes seraient paramétrées.
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 Workflow Challenge 1 : Extraction et identification de motifs à partir de données d'alimentation continue
Appel du workflow JMP et des assistants JSL. Voici un défi du jeudi pour vous. L'ensemble de données ci-joint est un ensemble de données de puissance de mon wattmètre en cours d'exécution. Ce que nous voulons faire, c'est identifier et étiqueter correctement les 8 motifs qui se produisent au cours de cet ensemble de données de signal d'environ 1000 points attaché à ce message, créer une nouvelle colonne avec l'ID de fonction et l'écrire de manière à pouvoir l'utiliser pour automatiser le marquage de données futures qui suivent cette même enveloppe.
Les hypothèses de collecte de données pour cet ensemble de données et l'avenir sont :
- Avant le début d'un motif, la puissance est de 0.
- Il y a une période avant et après les motifs qui sera non nulle (échauffement et décompression). Cela ne fait pas partie du système testé et ne doit pas être étiqueté.
- Chaque motif a une attaque, un maintien, un déclin et un relâchement. La forme de la fonction est identique d'un motif à l'autre. Il passe de 0 à un certain pic sur une période d'échantillons, puis descend progressivement jusqu'au bruit de fond de base (puissance de marche). L'ID de motif est terminé lorsque la puissance lit à nouveau 0.
- Il y aura un nombre inconnu de fonctions à identifier à l'avenir. Il y en a 8 dans le jeu de données. La solution doit être invariante à cela.
- La longueur totale des fonctions ainsi que le pic peuvent varier.
Nous travaillons sur le bord gauche de la workflow analytique aujourd'hui (en vert ci-dessous). Les données proviennent d'un fichier (.csv), et éventuellement, lorsque nous avons automatisé ce flux de travail, un dossier de n fichiers .csv. L'accès aux données est utilisé, et la tâche consiste aujourd'hui à effectuer les tâches de fusion et de nettoyage des données sur cet exemple de fichier pour que les données soient prêtes à étendre le flux de travail à d'autres capacités analytiques à l'avenir.

Laissez vos solutions dans les commentaires.
Fusion et nettoyage des données