Flusso di lavoro di JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continua
Richiamo del flusso di lavoro JMP e delle procedure guidate JSL. Ecco una sfida del giovedì per te. Il set di dati allegato è un set di dati di potenza dal mio misuratore di potenza in esecuzione. Quello che vogliamo fare è identificare e taggare correttamente gli 8 motivi che si verificano durante questo set di dati del segnale di ~ 1000 punti allegato a questo post, creare una nuova colonna con ID funzione e scriverla in modo che possa essere utilizzata per automatizzare l'etichettatura di dati futuri che seguono questa stessa busta.
Le ipotesi di raccolta dei dati per questo set di dati e per il futuro sono:
- Prima dell'inizio di un motivo la potenza è 0.
- C'è un periodo prima e dopo i motivi che saranno diversi da zero (riscaldamento e riscaldamento). Questo non fa parte del sistema sottoposto a test e non deve essere contrassegnato.
- Ogni motivo ha un attacco, sustain, decadimento e rilascio. La forma della funzione è identica da motivo a motivo. Va da 0 a un certo picco in un periodo di campioni e poi scende fino al rumore di fondo della linea di base (potenza di camminata). L'ID del motivo è finito quando la potenza legge di nuovo 0.
- Ci sarà un numero imprecisato di funzioni da identificare in futuro. Ce ne sono 8 nel set di dati. La soluzione deve essere invariante rispetto a quella.
- La lunghezza complessiva delle funzioni e il picco possono variare.
Stiamo lavorando sul bordo sinistro del flusso di lavoro analitico oggi (mostrato in verde sotto). I dati provengono da un file (.csv) e alla fine, quando abbiamo automatizzato questo flusso di lavoro, una cartella di N file .csv. Viene utilizzato l'accesso ai dati e l'attività attuale consiste nell'eseguire le attività di fusione e pulizia dei dati su questo file di esempio per avere i dati pronti per espandere il flusso di lavoro ad altre funzionalità analitiche in futuro.

Lascia le tue soluzioni nei commenti.
ScriptingRe: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaSono contento di sapere che il componente aggiuntivo funziona su Mac.
Hai ragione, ho dimenticato di aggiungere nuovamente il filtro per "sprint" dopo aver reso il componente aggiuntivo il più generico possibile.
Il seguente codice:
- utilizza il componente aggiuntivo per ottenere nuovi dati da Strava
- filtra le righe per includere solo i nomi "sprint".
- filtra le colonne in modo che corrispondano a quanto pubblicato nel set di dati "2022_03_28 Strava Sprinting Training Data Set 5 Runs"
- identifica i motivi
- aggiunge uno script alla tabella dei dati per rappresentare graficamente i risultati
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") );Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaFunziona su Mac, tuttavia, se inserisci il token di aggiornamento in modo errato, il componente aggiuntivo si rifiuterà di caricarsi e dovrai disinstallarlo e reinstallarlo. Sta estraendo tutte le attività recenti? Potrebbe essere utile avere una stringa di ricerca per inserire solo quelli con il titolo di interesse (ad esempio "sprint").
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaNon sono in grado di testarlo su un Mac, quindi la mia scelta predefinita era solo Windows.In allegato è presente un componente aggiuntivo che consente sia a Windows che a Mac. Non sono a conoscenza di un motivo per cui non dovrebbe funzionare, ma non è stato testato.
Per favore fatemi sapere se funziona correttamente su un Mac.
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaC'è qualche motivo per cui questo non funzionerebbe su macOS? Ho appena provato a caricarlo e potresti averlo creato solo per Windows.
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaHo pensato al mio approccio iniziale a questa sfida e ho pensato di poterlo fare meglio, quindi ho fatto un secondo tentativo.
Questa volta ho definito diverse forme di destinazione e quindi ho cercato tra i dati le regioni in cui le forme corrispondono ragionevolmente bene. Le mie forme target definite sono:

Questo metodo richiede meno parametri di regolazione e consente una maggiore flessibilità consentendo di aggiungere facilmente ulteriori forme di destinazione.
Ho aggiunto una colonna (watt modificati) che esegue un'estrapolazione lineare quando il valore dei watt diminuisce durante una corsa.
Ho anche aggiunto una colonna (Row State Formula) che nasconde ed esclude automaticamente i punti dati che non sono associati a un ID motivo.
A mio parere, questo è un miglioramento rispetto alla mia soluzione iniziale alla sfida.
Questo metodo identifica 41 corse dal set di dati "2022_03_28 Strava Sprinting Training Data Set 5 Runs.jmp"

Il codice JSL per elaborare i dati Strava è:
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 ) )
);
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaSono lieto di sapere che i watt stanno arrivando per te tramite l'API.
Ho apportato alcuni altri aggiornamenti al codice dell'API Strava che gestisce automaticamente l'aggiornamento dei codici di accesso, quindi l'ho inserito in un componente aggiuntivo generico che ho allegato.
La prima volta che esegui il componente aggiuntivo ti verrà chiesto di inserire le informazioni sull'API Strava

che viene salvato nella cartella dei componenti aggiuntivi in modo da non doverlo inserire nuovamente.
Il componente aggiuntivo restituisce una tabella di dati contenente tutte le attività disponibili da Strava.

Questo componente aggiuntivo dovrebbe semplificare ulteriormente il processo di acquisizione dei dati dall'API Strava, non solo per questo progetto ma anche per altri che desiderano esplorare i propri dati da Strava.
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaFantastico @ngambles . Funziona benissimo e grazie per aver pubblicato anche il link al video, che descrive come ottenere il token di accesso. Questi dati stanno arrivando perché mi piacerebbe vederli. E in più questo è un bel miglioramento rispetto all'esportazione manuale e alla loro combinazione in un file .csv. Pone davvero le basi per automatizzare questo prelievo di dati e ottenere che i dati vengano ripuliti per essere analizzati e pubblicati regolarmente su JMP Live. E sì, i watt stanno arrivando per me attraverso l'API dal tuo script.

ho incollato @brady_brady dall'alto, e ancora non ottiene tutti gli intervalli. Tuttavia, grazie a @ngambles 's Strava API code, abbiamo un'altra caratteristica interessante nel set di dati da esplorare e utilizzare, che è la colonna della velocità smussata. Penso che questo possa essere utile. Ora hai anche gli ID attività univoci con cui lavorare come un'altra funzionalità (sapendo che eseguirò solo tra 6-10 sprint in una determinata attività. Penso che dovremmo riproporre il modello ML, @Byron_JMP , ora che disponiamo di un set di dati di addestramento più ampio e di un paio di funzionalità aggiuntive per creare il modello.
Ho allegato il set di dati di addestramento aggiornato.

E @Giordania_Hiller secondo me dovresti provare anche tu! Sei un professionista della pulizia dei dati.
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaHo provato a ottenere i dati direttamente dall'API Strava.Sto ottenendo con successo misurazioni per distanza, velocità, posizione, altitudine e pendenza. Tuttavia non ricevo misurazioni di potenza.Ho il sospetto che ciò sia dovuto al livello del mio account Strava e/o alla mia mancanza di strumenti per la misurazione della potenza. Ho motivo di credere che il mio codice potrebbe funzionare per gli utenti che hanno dati di potenza nei loro dati Strava, ma non sono in grado di confermare. Se questa soluzione non è corretta, non posso immaginare che sia troppo lontana da ciò che è necessario.
ho trovato questo video Youtube molto istruttivo per iniziare con l'API Strava. Dimostra chiaramente come ottenere il tuo token di accesso dall'API Strava, che è necessario prima che il codice JSL riceva qualsiasi informazione da Strava. Nel codice seguente, sostituisci your_access_token_here con il tuo token di accesso che ottieni dall'API Strava prima dell'esecuzione.
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 );Il codice:
- esamina tutte le attività di Strava e seleziona solo quelle con "sprint" nel nome (senza distinzione tra maiuscole e minuscole)
- cerca di ottenere molte misurazioni inclusa la potenza (watt)
- organizza le informazioni in una tabella di dati simile ai set di dati originali forniti in questa sfida
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaHo provato a ottenere dati direttamente dall'API Strava. Sto ottenendo con successo diverse misurazioni tra cui distanza, velocità, longitudine, latitudine, altitudine e pendenza. Tuttavia non ricevo energia. Sospetto che ciò possa essere dovuto al mio account Strava e/o alla mia mancanza di attrezzatura per misurare la potenza. Questa soluzione può essere o meno quella necessaria per qualcuno con informazioni sull'alimentazione nel proprio account Strava, ma se non è corretta, non riesco a immaginare di essere troppo lontano.
Per iniziare con l'API Strava, questo Youtube il video è stato molto utile e mostra chiaramente i passaggi per ottenere il tuo token di accesso, che è necessario prima che questo codice possa ottenere dati da Strava. Nel codice seguente, sostituisci your_access_token_here con il tuo token di accesso effettivo, una volta che lo hai.
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 );
Il codice:
- include solo i dati per le attività che contengono "sprint", senza distinzione tra maiuscole e minuscole, nel nome
- cerca di ottenere potenza (watt), tra molte altre misurazioni tra cui distanza, velocità, posizione, altitudine, ecc...
- dispone i dati in un formato simile alle tabelle di dati originali presentate con questa sfida
Spero che questo codice funzioni per un utente che ha informazioni sull'alimentazione nel proprio account Strava.
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaBello, Brad.
Un buon punto su alcuni dei tempi piuttosto brevi dall'inizio di un intervallo rispetto a un abbandono. Tuttavia, gli abbandoni sembrano essere di 2-3 secondi al massimo. Vedremo come ciò regge con i nuovi dati raccolti questa settimana.
In allegato sono riportati alcuni dati di formazione aggiuntivi. Si tratta di circa 1:40 di corsa in condizioni molto stazionarie, ma a un ritmo più veloce (quasi il 100% della mia potenza critica). Potrebbe aiutare @Byron_JMP , però, con il suo modello ML. Forse aggiungere una funzione a questo set di dati di allenamento per avvicinarlo all'intervallo di riscaldamento/riscaldamento?
Inoltre, chi vuole affrontare l'ottenimento dei dati direttamente tramite l'API Strava utilizzando la richiesta HTTP (); in JMP? Lo so @dieter_pisot ha fatto questo prima. Posso contrassegnare gli allenamenti con qualcosa come gli sprint in modo che il pull dei dati possa essere facilmente recuperato tramite l'API.

Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaEcco un altro approccio con l'interpolazione dei dropout, utilizzando alcune colonne formula (relativamente semplici), molte delle quali potrebbero essere eliminate utilizzando una formula di interpolazione più complessa, a scapito di una facile risoluzione dei problemi e della scrittura di formule. (Vedi tabella allegata per formule e script grafici)
I risultati sembrano coerenti con ciò che vogliamo, almeno nel set di dati di esempio: (i punti rossi verranno eliminati)


Ecco come funziona in modo da poter giudicare se le ipotesi fatte valgono per i tuoi dati:
Innanzitutto, l'interpolazione degli zeri che si verificano a causa di "abbandoni". È stata scelta l'interpolazione lineare; sono state prese in considerazione spline e quadratiche ma i risultati erano inferiori ai miei occhi.
Nota le colonne da "Cum Zeros Behind" a "Interpolated Power" nelle righe 4-6 sottostanti.
"Cum Zeros Behind" è uno sguardo indietro inclusivo per gli zeri.
"Cum Zeros Ahead" è uno sguardo al futuro inclusivo per gli zeri.
"Zero Block" è il numero di zeri contigui nel blocco.
"Gruppi per zeri" inizia da 1 e aumenta ogni volta che si verifica un nuovo gruppo di zeri
L'interpolazione si verifica quando c'è un breve blocco di zeri (meno di 7), che non inizia dalla riga 1.
****Nota: nei dati di esempio, il caso più lungo di interpolazione si estendeva su 4 zeri, mentre la pausa "vera" più breve tra gli intervalli era di 7 zeri. Questo è un margine di errore piuttosto ridotto ed è uno svantaggio forse significativo di questo approccio. È importante che la pausa "vera" più breve sia più lunga del "abbandono" più lungo.
Come accennato in precedenza, la maggior parte di queste colonne non è necessaria, anche se la formula per l'interpolazione diventerà molto più brutta senza di esse e la risoluzione dei problemi sarà (molto) più difficile.

Dopo l'interpolazione, nei dati rimangono solo blocchi di almeno 7 zeri consecutivi (a eccezione degli zeri eventualmente presenti nelle prime righe).
La colonna "Intervallo" raggruppa i dati. Inizia da 1 e aumenta ogni volta che:
-il timestamp cambia di più di 100, o
-la potenza interpolata si sposta da 0 a un valore diverso da zero
La colonna "Post-spike" è un flag che viene impostato durante la discesa della potenza dal picco allo stato stazionario (più o meno).
È (ri)impostato a zero:
- alla riga 1
- ogni volta che la colonna dell'intervallo cambia
- ogni volta che si verifica un nuovo blocco di zeri
Imposta su 1 ogni volta che la potenza interpolata passa da un valore superiore a 90 a una potenza inferiore o uguale a 90. Sulla base dei dati del campione, ciò sembrava ragionevole.

Ora possiamo decidere quali dati ignorare: lo scopo della colonna "Ignora". Ignoriamo i dati quando una delle seguenti condizioni è vera:
- Il massimo nell'intervallo è < 400. (Questo è il "rumore di fondo"). Ancora una volta, sulla base dei dati forniti.
- La potenza interpolata supera i 100, dopo il picco. (Indica che siamo passati al "rumore di fondo", piuttosto che scendere verso lo 0)
- La potenza interpolata è 0
"Final Power" è uguale a "Interpolated Power", ma impostato su mancante quando "Ignore" == 1.
"Interval Time" calcola il tempo relativo all'intervallo (ovvero, inizia da 0 e aumenta da lì). Come "Final Power", viene impostato come mancante ogni volta che "Ignora" == 1.
Non ho disabilitato le valutazioni delle formule e ho suddiviso in sottoinsiemi i dati non ignorati, ma questo è probabilmente un passaggio successivo.
Sarà interessante vedere come Dan romperà questo prossimo;)
Saluti,
Brady
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaEcco tutte le soluzioni fino ad ora Pubblico JMP:
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaQuesta è davvero una bella soluzione. Ho pubblicato l'output su Pubblico JMP con un filtro dati locale per esaminare il nuovo set di dati, le tre esecuzioni di addestramento e la colonna delle caratteristiche dello stato stazionario binario:
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continua@Daniele_Valente ,
Ecco il mio tentativo di sfida.
Il mio approccio consiste nell'identificare le caratteristiche all'inizio e alla fine del motivo, identificare l'intera regione di esecuzione (tra l'inizio e la fine), quindi assegnare un ID motivo a ciascuna.
Ecco il mio codice per le formule di colonna che ho usato:
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 ) )
);Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaBello, Byron, sembra abbastanza buono. Sta individuando quelle due curve di rumore. Raccoglierò alcuni dati aggiuntivi durante il fine settimana e vedrò come va.

Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaCarino! Anche questa sembra una buona soluzione. Pubblica il JSL se riesci a scovarlo e lo proveremo.
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaHmm. Buone domande. È sempre utile esplorare le ipotesi quando si pensa a un problema di dati. E chiedi anche alle persone che possiedono il sistema che sta generando i dati!
1) Solo un riscaldamento di ricarica per sessione. Il riscaldamento non varia molto. La media è di ~250 +-30 watt a meno che non abbia bevuto qualche tazza di caffè...

2) Come puoi vedere, riesco davvero a mantenere la mia velocità massima solo per 6-10 secondi e poi la potenza inizia a diminuire. Forse in futuro saranno più lunghi/larghi. Dovremo esplorarlo.
3) Gli zeri dopo il periodo di camminata non dovrebbero essere inclusi. Sono un periodo di ripristino. È in gran parte 0 watt di preparazione dall'inizio. Sprint per un periodo di tempo. Rallenta fino a fermarti. Torna indietro. Torna a zero per l'insieme dell'intervallo successivo.
4) Gli zeri durante il motivo sono dati errati e devono essere interpolati. non ho smesso. È solo che il sensore è caduto. Dovrebbero essere corretti, contrassegnati come mancanti o interpolati altrimenti potrei vedere che causa alcuni problemi con l'analisi successiva.
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaHo cambiato l'approccio della semplice formula di colonna per includere un modello AI, quindi ora la tabella ha una formula straordinariamente complessa insieme ad alcune più semplici.
Lo script allegato aggiunge colonne e un grafico. Il modello valuta i segmenti della curva come segnale o rumore.
Sono interessato a vedere come segnerà un set di dati unico.

Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaHo fatto qualcosa di simile al lavoro in cui contavo i cicli di temperatura per il forno a shock termico. Penso che in quel caso l'ho fatto con un paio di diverse colonne formula, dovrò dare un'occhiata al codice al lavoro e vedere se potrebbe essere modificato per questo caso.
Se non ricordo male la mia idea era quella di rilevare il "punto di partenza" del ciclo (0 potenza in questo caso e ottenere l'ultimo valore di quelli), quindi verificare che ci siano abbastanza valori che superano una certa soglia (qui potrebbero essere 5 valori sopra 350) e successivamente controllare dove finisce il ciclo (parte piatta con ~20 valori tra 80-100). E poi ho alcune variabili in esecuzione (motid_id, cycle_ongoing, ...) che aumenterei e resetterei secondo necessità.
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaLe supposizioni contano. Cosa sei disposto ad assumere sui dati? Ecco dove staranno i tuoi problemi.
1) quanti cooldown/riscaldamenti sono possibili? Conosci la potenza media di questi? Varia?
2) I motivi possono variare in lunghezza... saranno comunque sempre di lunghezza simile, qualunque essa sia? Nei dati del campione fino ad ora, questo è stato il caso... rimarrà il caso in futuro?
3) E gli zeri? Dovrebbero essere inclusi come parte principale del motivo o no? Tutti, un certo numero, nessuno?
4) E gli zeri che si trovano a metà del motivo? Questi sono legittimi? Che dire del motivo centrale con punti diversi da zero? Come possiamo sapere se questi sono legittimi? Dovremmo provare?
ecc.ecc.ecc
Saluti,
Brady
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaEccolo con il terzo file concatenato con @Byron_JMP la sceneggiatura. Sembra che accada la stessa cosa. Stiamo contrassegnando parte dell'area da escludere come motivo. La cosa buona è che sembra fallire nello stesso punto o utilizzando la stessa modalità di errore. Puoi vederlo nel gruppo di cicli 9 e 18 e anche nel grafico campione/potenza.


Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaOh! Carino @Byron_JMP . Mi piace la soluzione della colonna formula. Ho appena provato a ridimensionarlo. Fallisce in modo diverso rispetto a @brady_brady la soluzione di.
Il motivo 9 sembra un po' sospetto...

Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaUh Oh! Problema quando sto cercando di ridimensionare questo. Ora abbiamo due giorni di dati. Corse la sceneggiatura di Brady per identificare, si spera, 16 Motivi.
Ho nuovi dati in arrivo ora e ho eseguito lo script @brady_brady ha scritto. Identificato correttamente 16 motivi. Ma c'è qualche stranezza nell'ottavo ora. Sembra che l'algoritmo sia confuso dai nuovi dati. Ho il timestamp, forse potrebbe aiutare a fare questa scala?


Via per provare @Byron_JMP la soluzione di.
Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continua@brady_brady Questo è un metodo abbastanza astuto per risolvere il problema.
L'ho fatto con tre semplici formule di colonna.

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
)));Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaBello, Brady! Ottimo lavoro. È interessante che ci siano anche alcuni problemi di qualità dei dati, posso adattare un modello di picco per appianarlo. Forse quelli potrebbero essere contrassegnati come mancanti perché non sono realmente 0 o interpolati?Re: Flusso di lavoro JMP Sfida 1: Estrazione e identificazione del motivo dai dati di potenza continuaÈ sorprendente quanto lontano puoi arrivare con una singola colonna formula:
dt << new column("Motif ID", nominal, formula(Col Cumulative Sum(
Row() > 6 & Summation( i = 1, 6, Abs( Lag( :power, i ) ) ) == 0 & :power != 0
)));
Tuttavia, c'è ancora un po' di pulizia da fare... questo approccio crea un ID gruppo "0", che è il dato principale, e non taglia l'ultimo motivo dove vorremmo. Il "6" nella sommatoria è perché designo un nuovo gruppo se si incontrano almeno 6 zeri consecutivi prima di un cambio di potenza.
Ovviamente ci sono molti altri modi per farlo che sono più sofisticati, che comportano confronti con i piani di fondo, attacchi/decadimenti, ecc... ma se un approccio semplice come questo funziona, bene.
Lo script seguente incorpora questa idea, ma ripulisce anche il set di dati. Idealmente i valori nelle ultime righe dovrebbero essere parametrizzati.
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--
);
