Desafío 1 del flujo de trabajo de JMP: Extracción e identificación de motivos a partir de datos de energía continua
Llamar al flujo de trabajo de JMP y a los asistentes de JSL. Aquí hay un desafío de jueves para ti. El conjunto de datos adjunto es un conjunto de datos de potencia de mi medidor de potencia en funcionamiento. Lo que queremos hacer es identificar y etiquetar correctamente los 8 motivos que ocurren durante este conjunto de datos de señal de ~1000 puntos adjunto a esta publicación, crear una nueva columna con ID de función y escribirla de manera que pueda usarse para automatizar el etiquetado. de datos futuros que siguen a este mismo sobre.
Los supuestos de recopilación de datos para este conjunto de datos y el futuro son:
- Antes del comienzo de un motivo, la potencia es 0.
- Hay un período antes y después de los motivos que será distinto de cero (calentamiento y calentamiento). Esto no es parte del sistema bajo prueba y no debe etiquetarse.
- Cada motivo tiene un ataque, sostenido, decaimiento y liberación. La forma de la función es idéntica de motivo a motivo. Va de 0 a algún pico durante un período de muestras y luego desciende hasta el nivel mínimo de ruido de referencia (potencia de paso). La identificación del motivo finaliza cuando el poder lee 0 nuevamente.
- Habrá un número desconocido de funciones para identificar en el futuro. Hay 8 en el conjunto de datos. La solución debe ser invariable a eso.
- La duración total de las funciones, así como el pico, pueden variar.
Estamos trabajando en el borde izquierdo de la flujo de trabajo analítico hoy (mostrado en verde a continuación). Los datos provienen de un archivo (.csv) y, finalmente, cuando automatizamos este flujo de trabajo, una carpeta de norte archivos .csv. Se está utilizando el acceso a datos, y la tarea actual es realizar las tareas de combinación y limpieza de datos en este archivo de ejemplo para tener los datos listos para expandir el flujo de trabajo a otras capacidades analíticas en el futuro.

Deja tus soluciones en los comentarios.
secuencias de comandosRe: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaMe alegra saber que el complemento funciona en Mac.
Tienes razón, olvidé volver a agregar el filtro para "sprint" después de hacer que el complemento fuera lo más genérico posible.
El siguiente código:
- utiliza el complemento para obtener datos actualizados de Strava
- filtra filas para incluir solo nombres de "sprint"
- filtra las columnas para que coincidan con lo que se publicó en el conjunto de datos "2022_03_28 Strava Sprinting Training Data Set 5 Runs"
- identifica motivos
- agrega script a la tabla de datos para trazar los resultados
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: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaFunciona en la Mac, sin embargo, si ingresa el token de actualización incorrectamente, el complemento se negará a cargarse y tendrá que desinstalarlo y volverlo a instalar. ¿Esto está extrayendo todas las actividades recientes? Podría ser bueno tener una cadena de búsqueda para extraer solo aquellos con el título de interés (es decir, "sprint").
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaNo puedo probarlo en una Mac, por lo que mi opción predeterminada fue solo Windows.Se adjunta un complemento que permite tanto Windows como Mac. No estoy al tanto de una razón por la que no debería funcionar, pero no se ha probado.
Por favor, hágame saber si se ejecuta correctamente en una Mac.
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continua¿Hay alguna razón por la que esto no funcione en macOS? Intenté cargarlo y es posible que lo hayas creado solo para Windows.
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaHe estado pensando en mi enfoque inicial para este desafío y pensé que podría hacerlo mejor, así que hice un segundo intento.
Esta vez, he definido varias formas objetivo y luego busco en los datos buscando regiones donde las formas coincidan razonablemente bien. Mis formas objetivo definidas son:

Este método requiere menos parámetros de ajuste y permite una mayor flexibilidad al permitir que se agreguen fácilmente formas de objetivos adicionales.
He agregado una columna (vatios modificados) que realiza una extrapolación lineal cuando el valor de vatios cae durante una ejecución.
También agregué una columna (fórmula de estado de fila) que automáticamente oculta y excluye puntos de datos que no están asociados con una ID de motivo.
En mi opinión, esta es una mejora con respecto a mi solución inicial al desafío.
Este método identifica 41 carreras del conjunto de datos "2022_03_28 Strava Sprinting Training Data Set 5 Runs.jmp"

El código JSL para procesar los datos de Strava es:
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: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaMe alegra saber que los vatios le llegan a través de la API.
Realicé algunas actualizaciones más en el código API de Strava que maneja la obtención automática de códigos de acceso actualizados, y luego lo incorporé a un complemento genérico que he adjuntado.
La primera vez que ejecute el complemento, se le pedirá que ingrese su información API de Strava

que se guarda en su carpeta de complementos para que no tenga que ingresarlo nuevamente.
El complemento devuelve una tabla de datos que contiene todas las actividades disponibles de Strava.

Este complemento debería agilizar aún más el proceso de obtención de datos de la API de Strava, no solo para este proyecto, sino también para otros que también quieran explorar sus datos de Strava.
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaFantástico @ngambles . Esto funciona muy bien y gracias por publicar el enlace al video también, que describe cómo obtener el token de acceso. Estos datos están llegando como me gustaría verlos. Y además, esta es una buena mejora con respecto a exportarlos manualmente y combinarlos en un archivo .csv. Realmente prepara el escenario para automatizar esta extracción de datos y limpiar los datos para analizarlos y publicarlos regularmente en JMP Live. Y sí, los vatios me llegan a través de la API de su secuencia de comandos.

pegué en @brady_brady las fórmulas de arriba, y todavía no obtiene todos los intervalos. Sin embargo, gracias a @ngambles del código API de Strava, tenemos otra característica interesante en el conjunto de datos para explorar y usar, que es la columna de velocidad suavizada. Creo que esto puede ser útil. Ahora también tiene las ID de actividad únicas para trabajar como otra función (sabiendo que solo ejecutaré entre 6 y 10 sprints en una actividad determinada). Creo que deberíamos lanzar el modelo ML nuevamente, @Byron_JMP , ahora que tenemos un conjunto de datos de entrenamiento más grande y un par de características adicionales para construir el modelo.
He adjuntado el conjunto de datos de entrenamiento actualizado.

Y @Jordan_Hiller ¡Creo que tú también deberías intentarlo! Eres un profesional de la limpieza de datos.
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaHe intentado obtener datos directamente de la API de Strava.Estoy obteniendo con éxito medidas de distancia, velocidad, posición, altitud y grado. Sin embargo, no obtengo mediciones de potencia.Tengo la sospecha de que esto se debe al nivel de mi cuenta de Strava y/o a mi falta de equipo de medición de potencia. Tengo razones para creer que mi código podría funcionar para usuarios que tienen datos de energía en sus datos de Strava, pero no puedo confirmarlo. Si esta solución no es correcta, no puedo imaginar que esté demasiado lejos de lo que se necesita.
encontré esto video de Youtube muy informativo para comenzar con la API de Strava. Demuestra claramente cómo obtener su token de acceso de la API de Strava, que se requiere antes de que el código JSL obtenga información de Strava. En el código a continuación, reemplace your_access_token_here con su token de acceso que obtiene de la API de Strava antes de ejecutar.
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 );El código:
- revisa todas las actividades de Strava y selecciona solo aquellas con "sprint" en sus nombres (sin distinción entre mayúsculas y minúsculas)
- intenta obtener muchas medidas, incluida la potencia (vatios)
- organiza la información en una tabla de datos similar a los conjuntos de datos originales provistos en este desafío
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaTomé una oportunidad para obtener datos directamente de la API de Strava. Estoy obteniendo con éxito varias medidas, incluidas la distancia, la velocidad, la longitud, la latitud, la altitud y el grado. Sin embargo, no estoy recibiendo energía. Sospecho que esto puede deberse a mi cuenta de Strava y/o mi falta de equipo para medir la potencia. Esta solución puede o no ser lo que necesita alguien con información de energía en su cuenta de Strava, pero si no es correcta, no puedo imaginar que esté demasiado lejos.
Para comenzar con la API de Strava, este YouTube El video fue muy útil y demuestra claramente los pasos para obtener su token de acceso, que se requiere antes de que este código obtenga datos de Strava. En el código a continuación, reemplace your_access_token_here con su token de acceso real, una vez que lo tenga.
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 );
El código:
- solo incluye datos para actividades que contienen "sprint", sin distinción entre mayúsculas y minúsculas, en el nombre
- trata de obtener potencia (vatios), entre muchas otras medidas como distancia, velocidad, posición, altitud, etc...
- organiza los datos en un formato similar a las tablas de datos originales presentadas con este desafío
Espero que este código funcione para un usuario que tiene información de energía en su cuenta de Strava.
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaBien, Brady.
Buen punto sobre algunos de los tiempos bastante cortos desde el comienzo de un intervalo frente a un abandono. Sin embargo, los abandonos parecen ser de 2 a 3 segundos como máximo. Veremos cómo se mantiene esto con la recopilación de nuevos datos esta semana.
Se adjuntan algunos datos de entrenamiento adicionales. Esto es aproximadamente 1:40 de funcionamiento muy constante, pero a un ritmo más acelerado (casi el 100% de mi poder crítico). Podría ayudar @Byron_JMP , sin embargo, con su modelo ML. ¿Quizás agregar una función a este conjunto de datos de entrenamiento para acercarlo al rango de calentamiento/recalentamiento?
Además, ¿quién quiere abordar la obtención de datos directamente a través de la API de Strava utilizando HTTP Request (); en JMP? Sé @dieter_pisot ha hecho esto antes. Puedo etiquetar los entrenamientos con algo como sprints para que la extracción de datos se pueda recuperar fácilmente a través de la API.

Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaAquí hay otro enfoque con la interpolación de los abandonos, usando algunas columnas de fórmula (relativamente simples), muchas de las cuales podrían eliminarse usando una fórmula de interpolación más compleja, a expensas de la resolución de problemas y la redacción de fórmulas. (Consulte la tabla adjunta para ver fórmulas y secuencias de gráficos)
Los resultados parecen coherentes con lo que queremos, al menos en el conjunto de datos de muestra: (se eliminarán los puntos rojos)


Así es como funciona para que pueda juzgar si las suposiciones realizadas son válidas para sus datos:
Primero, la interpolación de los ceros que ocurren debido a "abandonos". Se eligió la interpolación lineal; Se consideraron splines y cuadráticos, pero los resultados fueron inferiores a mi ojo.
Tenga en cuenta las columnas "Cum Zeros Behind" a "Interpolated Power" en las filas 4-6 a continuación.
"Cum Zeros Behind" es una mirada retrospectiva inclusiva para los ceros.
"Cum Zeros Ahead" es una anticipación inclusiva de ceros.
"Zero Block" es el número de ceros contiguos en el bloque.
"Grupos por ceros" comienza en 1 y aumenta cada vez que se produce un nuevo grupo de ceros
La interpolación ocurre cuando hay un bloque corto (menos de 7) de ceros, que no comienza en la fila 1.
****Nota: En los datos de muestra, el caso más largo de interpolación abarcó 4 ceros, mientras que la ruptura "verdadera" más corta entre intervalos fue de 7 ceros. Este es un margen de error bastante pequeño y es un inconveniente posiblemente significativo de este enfoque. Es importante que la pausa "verdadera" más corta sea más larga que la "deserción" más larga.
Como se mencionó anteriormente, no necesita la mayoría de estas columnas, aunque la fórmula para la interpolación se volverá mucho más fea sin ellas, y la resolución de problemas será (mucho) más difícil.

Después de la interpolación, solo quedan bloques de al menos 7 ceros consecutivos en los datos (excepto los ceros que posiblemente aparezcan en las primeras filas).
La columna "Intervalo" agrupa los datos. Comienza en 1 y se incrementa cada vez que:
-la marca de tiempo cambia en más de 100, o
-la potencia interpolada se mueve de 0 a algún valor distinto de cero
La columna "Post-pico" es una bandera que se establece durante la disminución de la potencia desde el pico hasta el estado estable (más o menos).
Se (re)pone a cero:
- en la fila 1
- cada vez que cambia la columna de intervalo
- cada vez que se produce un nuevo bloque de ceros
Se establece en 1 siempre que la potencia interpolada se mueva de algún valor por encima de 90 a alguna potencia menor o igual a 90. Según los datos de la muestra, esto parecía razonable.

Ahora podemos decidir qué datos ignorar: el propósito de la columna "Ignorar". Ignoramos los datos cuando cualquiera de los siguientes es cierto:
- El máximo sobre el intervalo es < 400. (Este es el "ruido de fondo"). Una vez más, en base a los datos proporcionados.
- La potencia interpolada se mueve por encima de 100, después del pico. (Lo que indica que nos hemos movido a "ruido de fondo", en lugar de bajar hacia 0)
- La potencia interpolada es 0
"Potencia final" es lo mismo que "Potencia interpolada", pero se establece en falta cuando "Ignorar" == 1.
"Tiempo de intervalo" calcula el tiempo en relación con el intervalo (es decir, comienza en 0 y se incrementa a partir de ahí). Al igual que "Poder final", se configura como perdido cada vez que "Ignorar" == 1.
No deshabilité las evaluaciones de fórmulas ni subestablecí los datos no ignorados, pero es probable que este sea el siguiente paso.
Será interesante ver cómo Dan rompe esto a continuación;)
Salud,
Brady
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaAquí están todas las soluciones hasta ahora en Público JMP:
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaEsta es una muy buena solución. Publiqué la salida en JMP público con un filtro de datos local para ver el nuevo conjunto de datos, las tres ejecuciones de entrenamiento y su columna característica de estado estacionario binario:
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continua@Daniel_Valente ,
Aquí está mi intento de desafío.
Mi enfoque es identificar las características al principio y al final del motivo, identificar toda la región de ejecución (entre el principio y el final) y luego asignar una ID de motivo a cada una.
Aquí está mi código para las fórmulas de columna que utilicé:
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: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaBien, Byron, esto se ve muy bien. Está seleccionando esas dos curvas de ruido. Recopilaré algunos datos adicionales durante el fin de semana y veré cómo funciona.

Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continua¡Lindo! Eso suena como una buena solución también. Publique el JSL si puede desenterrarlo y lo intentaremos.
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaMmm. Buena pregunta. Siempre es bueno explorar suposiciones cuando se piensa en un problema de datos. ¡Y también pregunte a las personas propietarias del sistema que genera los datos!
1) Solo un calentamiento de enfriamiento por sesión. El calentamiento realmente no varía tanto. La media es como ~250 +-30 vatios a menos que haya tomado algunas tazas de café...

2) Como puede ver, solo puedo mantener mi velocidad máxima durante 6-10 segundos y luego la potencia comienza a disminuir. Tal vez en el futuro sean más largos/más anchos. Tendremos que explorar eso.
3) No se deben incluir ceros después del período de caminata. Son un período de reinicio. Es en gran medida una preparación de 0 vatios desde el principio. Sprint durante un período de tiempo. Reduzca la velocidad hasta detenerse. Camina de vuelta. Vuelve a cero para el conjunto del siguiente intervalo.
4) Los ceros durante el motivo son datos incorrectos y deben interpolarse. no he parado Es solo que el sensor se ha caído. Deben corregirse, marcarse como faltantes o interpolarse; de lo contrario, podría ver que causa algunos problemas con el análisis posterior.
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaCambié el enfoque de fórmula de columna simple para incluir un modelo de IA, por lo que ahora la tabla tiene una fórmula fantásticamente compleja junto con algunas más simples.
El script adjunto agrega columnas y un gráfico. El modelo puntúa los segmentos de la curva como señal o ruido.
Estoy interesado en ver cómo calificará un conjunto de datos único.

Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaHice algo un poco similar en el trabajo donde estaba contando los ciclos de temperatura para el horno de choque térmico. Creo que en ese caso lo hice con un par de columnas de fórmula diferentes, tendré que echar un vistazo al código en funcionamiento y ver si se puede modificar para este caso.
Si no recuerdo mal, mi idea era detectar el "punto de inicio" del ciclo (0 potencia en este caso y obtener el último valor de esos), luego verifique que haya suficientes valores que estén por encima de algún umbral (aquí podría ser 5 valores por encima 350) y luego verifique dónde termina el ciclo (parte plana con ~20 valores entre 80-100). Y luego tener algunas variables en ejecución (motid_id, cycle_ongoing, ...) que aumentaría y restablecería según sea necesario.
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaLas suposiciones importan. ¿Qué está dispuesto a asumir acerca de los datos? Allí es donde radicarán sus problemas.
1) ¿Cuántos enfriamientos/calentamientos son posibles? ¿Conoces la potencia media de estos? ¿Varía?
2) Los motivos pueden variar en longitud... Sin embargo, ¿tendrán siempre la misma longitud, sea lo que sea? En los datos de muestra hasta ahora, este ha sido el caso... ¿seguirá siendo el caso en el futuro?
3) ¿Qué pasa con los ceros? ¿Deberían incluirse como parte principal del motivo, o no? ¿Todos ellos, un cierto número de ellos, ninguno de ellos?
4) ¿Qué pasa con los ceros que aparecen en medio del motivo? ¿Son estos legítimos? ¿Qué pasa con el motivo medio de puntos distintos de cero? ¿Cómo podemos saber si estos son legítimos? ¿Deberíamos intentarlo?
etc etc etc
Salud,
Brady
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaAquí está con el tercer archivo concatenado con @Byron_JMP guión de . Parece que ocurre lo mismo. Estamos etiquetando parte del área para excluirla como motivo. Lo bueno es que parece fallar en el mismo lugar o usando el mismo modo de falla. Puedes verlo en el grupo de ciclos 9 y 18 y también en el diagrama de muestra/potencia.


Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continua¡Ooo! Lindo @Byron_JMP . Me gusta la solución de la columna de fórmula. Solo intenté escalarlo. Falla de una manera diferente a @brady_brady la solución de .
Motif 9 parece un poco sospechoso...

Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continua¡UH oh! Problema cuando estoy tratando de escalar esto. Ahora tenemos dos días de datos. Ejecutó el guión de Brady para identificar, con suerte, 16 motivos.
Tengo nuevos datos ingresando ahora y ejecuté el script @brady_brady escribió. Identificó correctamente 16 motivos. Pero ahora hay alguna rareza en el octavo. Parece que el algoritmo está confundido por los nuevos datos. Tengo la marca de tiempo, ¿tal vez eso podría ayudar a hacer esta escala?


fuera a probar @Byron_JMP la solución de .
Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continua@brady_brady Ese es un método bastante ingenioso para resolver el problema.
Lo hice con tres fórmulas de columna 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
)));Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continua¡Bien, Brady! Buen trabajo. Es interesante que también haya algunos problemas de calidad de datos, puedo ajustar un modelo máximo para suavizar eso. ¿Quizás esos podrían marcarse como faltantes porque en realidad no son 0 o interpolados?Re: JMP Workflow Challenge 1: Extracción e identificación de motivos a partir de datos de energía continuaEs sorprendente lo lejos que se puede llegar con una sola columna de fórmula:
dt << new column("Motif ID", nominal, formula(Col Cumulative Sum(
Row() > 6 & Summation( i = 1, 6, Abs( Lag( :power, i ) ) ) == 0 & :power != 0
)));
Sin embargo, todavía queda algo de limpieza por hacer... este enfoque crea un ID de grupo "0", que es el dato principal, y no corta el último motivo donde nos gustaría. El "6" en la suma se debe a que designo un nuevo grupo si se encuentran al menos 6 ceros consecutivos antes de un cambio de potencia.
Por supuesto, hay muchas otras formas de hacer esto que son más sofisticadas, que involucran comparaciones con los pisos de fondo, ataques/decaimientos, etc... pero si un enfoque simple como este funciona, genial.
El siguiente script incorpora esta idea, pero también limpia el conjunto de datos. Idealmente, los valores en las últimas líneas estarían parametrizados.
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--
);
