datacubeR

Un algoritmo para detectar máximos y mínimos en Pandas

Cambios de Signo

Detectando los Cambios de Signo en una curva

picture of me picture of me

En Jooycar nos dedicarmos a analizar comportamiento de manejo, y nos tuvimos que enfrentar a un problema muy pequeño en el que queríamos detectar si había una aceleración o un frenado, midiendo la curva de Fuerza G. En esta curva nosotros tenemos valores positivos, que representan que se está acelerando y valores negativos que representa que se está frenando. Para nosotros controlar estos valores es de extrema importancia ya que nos interesa velar por la seguridad del conductor y que un conductor se exponga a cambios extremos puede ser peligroso para su integridad física. Nuestro interés en específico era analizar el valor máximo de cada evento de Aceleración y de Frenado. Eso quiere decir que teníamos que encontrar los siguientes puntos:

png

El gráfico muestra una curva de Fuerza G ficticia, pero de comportamiento similar, donde hay sectores sobre cero (positivos) y sectores bajo cero (negativos). El objetivo final es encontrar los puntos en negro que serán los valores máximos o mínimos, de cada uno de los peaks o valles que se vayan dando.

Para modelar esto se utilizarán los siguiente datos como un pandas DataFrame:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
df = pd.DataFrame({"Eventos": [1,2,1,1,-3,-4,7,8,9,10,-3,5,6,7,-10]})
df.plot(figsize = (10,8))
plt.axhline(y = 0, c = 'r')
plt.show()

png

Teniendo los mismos datos, crear la lógica fue un poco compleja, pero muy similar al cálculo de la recencia. Iremos haciendo el cálculo columna por columna para que se entienda la lógica. Nuestra implemetación final no requiere la creación de columnas intermedias:

df["sign"]=np.sign(df.Eventos)
df['diff_sign'] = df.sign.diff()
df["change"]=np.cumsum(df.sign.diff() != 0)
df
eventssigndiff_signchange
011NaN1
1210.01
2110.01
3110.01
4-3-1-2.02
5-4-10.02
6712.03
7810.03
8910.03
91010.03
10-3-1-2.04
11512.05
12610.05
13710.05
14-10-1-2.06

Expliquemos un poco el algoritmo. Como se puede ver primero se aplica un np.sign. Esta función permite determinar el signo, positivo (1) o negativo (-1) del evento. Luego se aplico un .diff(). Esta función permite calcular la diferencia entre el valor siguiente y el anterior. Dado que la primera fila del Dataframe no tiene anterior es que se rellena con NaN.

Prestando atención a los resultados de la columna diff_sign es posible observar que los resultados son siempre cero, excepto en los lugares donde ocurre un cambio de signo.

Es a partir de ahí que nace la lógica de la columna change. Esta columna va a sumar de manera acumulativa todos los valores que no sean 0. Al hacer esto, vemos que cada sección, positiva o negativa, va a quedar asociada a un número. Es decir, pudimos crear un identificador de grupo, donde cada grupo es una sección que se va alternando entre el valor positivo y negativo.

Para poder calcular los valores máximos por grupo, basta con agrupar:

df["change"]=np.cumsum(df.sign.diff() != 0)
valores = df.groupby("change").events.transform(lambda x: x.abs().max())*df.sign
valores
0      2
1      2
2      2
3      2
4     -4
5     -4
6     10
7     10
8     10
9     10
10    -3
11     7
12     7
13     7
14   -10
dtype: int64

El resultado que se almacena en valores corresponderá al máximo para cada grupo. Este valor se encuentra repetido para cada instancia del grupo. Nótese que para evitar utilizar el mínimo o el máximo dependiendo si es peak o valle, se calcula sólo el máximo al valor absoluto de las instancias del grupo.

Finalmente para calcular los puntos y graficarlos se hace el siguiente truco: Si El evento es igual al valor máximo calculado entonces se deja como True, sino como NaN

mask = (df.Eventos == valores).mask(lambda x: x == 0, np.nan)
mask
0      NaN
1      2.0
2      NaN
3      NaN
4      NaN
5     -4.0
6      NaN
7      NaN
8      NaN
9     10.0
10    -3.0
11     NaN
12     NaN
13     7.0
14   -10.0
dtype: float64

Para terminar graficaremos nuestra curva inicial con la multiplicación del evento con mask lo que dejará visibles sólo los puntos máximos o mínimos según corresponda.

df.events.plot(figsize = (10,8))
plt.axhline(y = 0, c = 'r')
plt.plot(df.Eventos*mask, 'o', c = 'k')
plt.show()

png

Utilizando 3 funciones simples es posible resolver un problema de relativa complejidad para poder dar solución a una feature mediante análisis de Datos.

Nos vemos en la próxima,

Alfonso

Go to top