2.3. Descriptor estadístico de un punto o dispositivo

Los datos registrados para un punto, tabulados en el proceso anterior requieren un análisis básico para obtener descriptores estadísticos que los representen. A partir del archivo ejemplo mostrado, se pueden obtener los siguientes resultados:

data_LOS22.csv


Por ejemplo, para analizar el nivel de señal (rssi_up, rssi_down) se requiere de los parámetos de media, desviación estándar, medias móviles, función de probabilidad de masa (pmf), etc. Los parámetros se pueden resumir en tablas y gráficas semejantes a la mostrada.

descriptor estadístico para los datos de la gráfica

descriptor
               rssi_up  rssi_down
count        98.000000   98.00000
mean        -86.336735  -77.44898
std           3.782608    1.14083
min         -97.000000  -81.00000
25%         -89.000000  -78.00000
50%         -86.000000  -77.00000
75%         -84.000000  -77.00000
max         -78.000000  -75.00000
error_trama   0.000000    0.00000

Para el proceso se requiere el nombre del punto, la carpeta o directorio donde se encuentra, la medida a observar (ej: rssi).

Un parámetro como la media puede se insuficiente para observar el comportamiento, por lo que se añaden las medias móviles.

Para la media móvil se debe indicar cada cuántas muestras se obtendrá el promedio, por lo que al usar varias se darán varias de ellas mediante la variable movAvg_cual.

Errores en una trama

Se consideran errores de medida a los valores fuera de un intervalo expresado en «medida_normal». Al indicar si un dato tiene un error es posible determinar la tasa de error en de trama, para el canal de subida y de bajada.

Registros a procesar

Los registros que se procesarán serán libres de errores de trama, donde se calculan la media, desviación estándar, mínimo, máximo. Los resultados son los mostrados en la tabla.

Funciones de Probabilidad de masa (pmf)

Para cada valor de medida (rssi), se obtiene la frecuencia relativa que se presentan en una gráfica pmf.

Archivos de resultado

Los resultados se guardan en archivos.csv y graficos.pmf para el registro del análisis de cada punto. Loa archivos se agrupan en una carpeta de resultados para su posterior análisis en conjunto.

describe_LOS22.csv


Instrucciones en Python

# Descriptores estadisticos de los datos.csv
# de un dispositivo, revisa media, desviació estándar, pmf
# graba unreporte.csv con pandas y genera gráficas
# http://blog.espol.edu.ec/girni/

import numpy as np
import json
import pandas as pd
import datetime as dt
import os
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.units as munits

# INGRESO
cualpunto   = "data_m_LOS22.csv"
carpeta_rsm = "resultado_Test"

medida         = "rssi"
medida_unidad  = "dBm"
medida_normal  = [-250,-1]
medida_grafica = [-100,-60]

movAvg_cual  = [8,16] #cada cuantas muestras
movAvg_color = ['lightgreen','orange']
precision   = 2
guarda      = True

# PROCEDIMIENTO
unmodelo = cualpunto.strip('.csv').split('_')[1]
unubicado   = cualpunto.strip('.csv').split('_')[2]
codigopunto = unmodelo+'_'+unubicado

# Leer archivo
archivopunto = carpeta_rsm + '/' + cualpunto
tabla = pd.read_csv(archivopunto)
tabla = tabla.drop(columns='Unnamed: 0')
tabla = pd.DataFrame(tabla)

fechaformato = "%Y-%m-%d %H:%M:%S.%f"
# medida intervalo
medida_min = np.min(medida_normal)
medida_max = np.max(medida_normal)

# fechas series a datetime
tabla['publishedAt'] = pd.to_datetime(tabla['publishedAt'],
                                      format=fechaformato)
tabla['created'] = pd.to_datetime(tabla['publishedAt'],
                                  format=fechaformato)

# revisa errores de medida
tabla["error_up"]   = 0
tabla["error_down"] = 0
for undato in tabla['publishedAt'].keys():   
    medida_up = tabla[medida+'_up'][undato]
    enrango = (medida_up>=np.min(medida_normal))
    enrango = (enrango and medida_up<=np.max(medida_normal))
    if not(enrango):
        tabla.at[undato,"error_up"] = 1
    
    medida_down = tabla[medida+'_down'][undato]
    enrango = (medida_down>=np.min(medida_normal))
    enrango = (enrango and medida_down<=np.max(medida_normal))
    if not(enrango):
        tabla.at[undato,"error_down"] = 1      

# tasa error trama
leidos = len(tabla)
if leidos > 0:
    error_up   = np.sum(tabla['error_up'])
    error_up   = error_up/leidos
    error_down = np.sum(tabla['error_down'])
    error_down = error_down/leidos

# descriptor estadístico, datos sin errores
condicion_up = (tabla['error_up']==0)
condicion_down = (tabla['error_down']==0)

medida_up = tabla[condicion_up][medida+'_up']
describe_up = medida_up.describe()
describe_up['error_trama'] = error_up

medida_down = tabla[condicion_down][medida+'_down']
describe_down = medida_down.describe()
describe_down['error_trama'] = error_down

descriptor = describe_up.copy()
descriptor = pd.concat([descriptor,describe_down],axis=1)
descriptor['dispositivo'] = tabla['dispositivo'][0]

# función de probabilidad de masa pmf
def medida_pmf(valores,undescriptor):
    pmin   = np.min(valores)
    pmax   = np.max(valores)
    tramo  = int(pmax-pmin)
    conteo = np.zeros(tramo+1,dtype=int)
    intervalo = np.arange(pmin,pmax+1,1)
    for valor in valores:
        donde = np.where(intervalo == valor)
        conteo[donde] = conteo[donde] + 1
    freq_relativa = np.array(conteo)/np.sum(conteo)
    unpmf = {'intervalo' : list(intervalo),
             'freq_relativa' : list(freq_relativa)}
    return(unpmf)

pmf_up   = medida_pmf(medida_up,describe_up)
pmf_down = medida_pmf(medida_down,describe_down)

pmf_punto = {'pmf':{'pmf_up'   : pmf_up,
                    'pmf_down' : pmf_down}}
pmf_punto = pd.DataFrame(pmf_punto)
pmf_punto['dispositivo'] = tabla['dispositivo'][0]

# Para gráficas
# medias moviles en movAvg_cual[]
serie_up  = pd.Series(medida_up)
movAvg_up_mean = []
movAvg_up_std = []
m = len(movAvg_cual)
for j in range(0,m,1):
    k = movAvg_cual[j]
    movAvg_up_mean.append(list(serie_up.rolling(k).mean()))
    movAvg_up_std.append(list(serie_up.rolling(k).std()))
    
serie_down = pd.Series(medida_down)
movAvg_down_mean = []
movAvg_down_std = []
for j in range(0,m,1):
    k = movAvg_cual[j]
    movAvg_down_mean.append(list(serie_down.rolling(k).mean()))
    movAvg_down_std.append(list(serie_down.rolling(k).std()))

movAvgData ={'movAvg_cual'   : movAvg_cual,
             'movAvg_color'  : movAvg_color,
             'movAvg_up_mean'  : movAvg_up_mean,
             'movAvg_down_mean': movAvg_down_mean,
             'movAvg_up_std'   : movAvg_up_std,
             'movAvg_down_std' : movAvg_down_std
             }

grafData ={'codigopunto' : codigopunto,
           'medida' : medida,
           'precision': precision,
           'medida_unidad' : medida_unidad,
           'medida_grafica': medida_grafica
           }

# SALIDA --------------------------
print('descriptor')
print(descriptor)

# guarda el reporte en csv
unarchivo = carpeta_rsm+'/describe_'+codigopunto+'.csv'
descriptor.to_csv(unarchivo)

unarchivo = carpeta_rsm+'/pmf_'+codigopunto+'.json'
pmf_punto.to_json(unarchivo)

unarchivo = carpeta_rsm+'/movavg_'+codigopunto+'.json'
with open(unarchivo, "w") as outfile:
    json.dump(movAvgData, outfile)

unarchivo = carpeta_rsm+'/grfdata_'+codigopunto+'.json'
with open(unarchivo, "w") as outfile:
    json.dump(grafData, outfile)


# GRAFICA -----
def graf_puntos_serie(tabla,descriptor,movAvgData,grafData):
    ''' grafica la serie de tiempo de cada punto
        añade medias móviles
    '''
    # ajuste de formato de fecha para eje x
    converter = mdates.ConciseDateConverter()
    munits.registry[np.datetime64] = converter
    munits.registry[dt.date] = converter
    munits.registry[dt.datetime] = converter

    # datos para grafica
    precision   = grafData['precision']
    medida = grafData['medida']
    medida_unidad  = grafData['medida_unidad']
    medida_grafica = grafData['medida_grafica']
    
    movAvg_cual  = movAvgData['movAvg_cual']
    movAvg_color = movAvgData['movAvg_color']
    
    media_up    = descriptor[medida+'_up']['mean']
    std_up      = descriptor[medida+'_up']['std']
    media_down  = descriptor[medida+'_down']['mean']
    std_down    = descriptor[medida+'_down']['std']

    # ajuste de intervalo eje y
    y_min = np.min([np.min(medida_grafica),
                    media_up - 2*std_up,
                    media_down - 2*std_down])
    y_max = np.max([np.max(medida_grafica),
                    media_up + 2*std_up,
                    media_down + 2*std_down])
    
    # selecciona sin error
    condicion_up = (tabla['error_up']==0)
    condicion_down = (tabla['error_down']==0)

    # grafica
    fig_serie,(graf_up,graf_down) = plt.subplots(2,1)
    
    # medida_up -----
    graf_up.plot(tabla[condicion_up]['publishedAt'],
                 tabla[condicion_up][medida+'_up'],
                 color='blue',marker ='.',
                 linestyle='')
    
    # medida_up, medias y std
    etiq_up = str(np.round(media_up,precision))+' +/- '
    etiq_up = etiq_up + str(np.round(std_up,precision))
    graf_up.axhline(media_up,
                    color='blue',label=etiq_up)
    graf_up.axhline(media_up-std_up,
                    color='blue',linestyle='dotted')
    graf_up.axhline(media_up+std_up,
                    color='blue',linestyle='dotted')
    
    # medida_up, medias móviles
    m = len(movAvg_cual)
    for j in range(0,m,1):
        k = str(movAvg_cual[j])
        graf_up.plot(tabla[condicion_up]['publishedAt'],
                     movAvgData['movAvg_up_mean'][j],
                     label='movAvg_'+k,
                     color=movAvg_color[j])
    
    graf_up.set_ylim(y_min,y_max)
    graf_up.set_ylabel(medida+'_up ('+medida_unidad+')',
                       color='blue')
    graf_up.legend()
    graf_up.grid(True,linestyle='dotted',
                 axis='x',which='both')

    # medida_down -------
    graf_down.plot(tabla[condicion_down]['publishedAt'],
                   tabla[condicion_down][medida+'_down'],
                   color='brown',marker ='.',
                   linestyle='')

    # medida_down, medias y std
    etiq_down = str(np.round(media_down,precision))+' +/- '
    etiq_down = etiq_down + str(np.round(std_down,precision))
    graf_down.axhline(media_down,
                      color='brown',label=etiq_down)
    graf_down.axhline(media_down+std_down,
                      color='brown',linestyle='dotted')
    graf_down.axhline(media_down-std_down,
                      color='brown',linestyle='dotted')
    
    # medida_down, medias moviles
    for j in range(0,m,1):
        k = str(movAvg_cual[j])
        graf_down.plot(tabla[condicion_down]['publishedAt'],
                       movAvgData['movAvg_down_mean'][j],
                       label='movAvg_'+k,
                       color=movAvg_color[j])
    
    graf_down.set_ylim(y_min,y_max)
    graf_down.set_xlabel('fecha')
    graf_down.set_ylabel(medida+'_down ('+medida_unidad+')',
                         color='brown')
    graf_down.legend()
    graf_down.grid(True,linestyle='dotted',
                   axis='x', which='both')
    graf_up.set_title('Serie: '+grafData['codigopunto']+' '+ medida)
    plt.tight_layout()
    return(fig_serie)

def graf_puntos_pmf(pmf_punto,descriptor,grafData):
    ''' grafica función de probabilida de masa
        para cada punto, media +/- std
    '''
    # datos para grafica
    x_pmfup   = pmf_punto['pmf']['pmf_up']['intervalo']
    y_pmfup   = pmf_punto['pmf']['pmf_up']['freq_relativa']
    x_pmfdown = pmf_punto['pmf']['pmf_down']['intervalo']
    y_pmfdown = pmf_punto['pmf']['pmf_down']['freq_relativa']
    
    precision   = grafData['precision']
    medida = grafData['medida']
    medida_unidad  = grafData['medida_unidad']
    medida_grafica = grafData['medida_grafica']
    
    media_up   = descriptor[medida+'_up']['mean']
    std_up     = descriptor[medida+'_up']['std']
    media_down = descriptor[medida+'_down']['mean']
    std_down   = descriptor[medida+'_down']['std']

    prob_max = 0.40
    # ajuste de intervalo eje y
    y_min = np.min([np.min(medida_grafica),
                    media_up - 2*std_up,
                    media_down - 2*std_down])
    y_max = np.max([np.max(medida_grafica),
                    media_up + 2*std_up,
                    media_down + 2*std_down])
    # grafica
    fig_pmf,graf_pmf = plt.subplots()
    etiq_up = str(np.round(media_up,precision)) +' +/- '
    etiq_up = etiq_up + str(np.round(std_up,precision))
    graf_pmf.plot(x_pmfup,y_pmfup,
                  label='media_up '+etiq_up,
                  color='blue')
    graf_pmf.axvline(media_up,color='blue')
    graf_pmf.axvline(media_up+std_up,
                     linestyle='dotted',color='blue')
    graf_pmf.axvline(media_up-std_up,
                     linestyle='dotted',color='blue')

    etiq_down = str(np.round(media_down,precision))+' +/- '
    etiq_down = etiq_down + str(np.round(std_down,precision))
    graf_pmf.plot(x_pmfdown,y_pmfdown,
                  label='media_down '+etiq_down,
                  color='brown')
    graf_pmf.axvline(media_down,color='brown')
    graf_pmf.axvline(media_down+std_down,
                     linestyle='dotted',color='brown')
    graf_pmf.axvline(media_down-std_down,
                     linestyle='dotted',color='brown')

    graf_pmf.set_title('pmf: '+grafData['codigopunto']+' '+medida)
    graf_pmf.set_xlim(y_min,y_max)
    graf_pmf.set_ylim(0,prob_max)
    graf_pmf.set_xlabel(medida+' ('+medida_unidad+')')
    graf_pmf.set_ylabel('frecuencia relativa')
    graf_pmf.legend()
    graf_pmf.grid(True,linestyle='dotted',
                  axis='x', which='both')
    return(fig_pmf)

def graf_puntos_std(tabla,descriptor,movAvgData,grafData):
    ''' grafica serie de std usando medias moviles
        para cada punto, media_std
    '''
    # ajuste de formato de fecha para eje x
    converter = mdates.ConciseDateConverter()
    munits.registry[np.datetime64] = converter
    munits.registry[dt.date] = converter
    munits.registry[dt.datetime] = converter
    
    # datos para grafica
    precision = grafData['precision']
    medida    = grafData['medida']
    medida_unidad = grafData['medida_unidad']

    movAvg_cual = movAvgData['movAvg_cual']
    movAvg_color = movAvgData['movAvg_color']

    # selecciona sin error
    condicion_up   = (tabla['error_up']==0)
    condicion_down = (tabla['error_down']==0)
    
    # ajuste de intervalo eje y
    y_min = 0
    y_max = np.max([2, 2*descriptor[medida+'_up']['std'],
                    2*descriptor[medida+'_down']['std']])
    # grafica
    fig_std,(graf_stdUp,graf_stdDown) = plt.subplots(2,1)
    
    # std up
    std_up = np.round(descriptor[medida+'_up']['std'],precision)
    graf_stdUp.axhline(std_up,label='std '+str(std_up),
                       color='blue')
    m = len(movAvg_cual)
    for j in range(0,m,1):
        k = str(movAvg_cual[j])
        graf_stdUp.plot(tabla[condicion_up]['publishedAt'],
                        movAvgData['movAvg_up_std'][j],
                        label='movAvg_'+k,
                        color=movAvg_color[j])
    graf_stdUp.set_ylim(y_min,y_max)
    graf_stdUp.set_ylabel('std_up ('+medida_unidad+')',
                          color='blue')
    graf_stdUp.legend()
    graf_stdUp.grid(True,linestyle='dotted',
                    axis='x', which='both')
    graf_stdUp.set_title('std: '+grafData['codigopunto']+' '+ medida)

    # std down
    std_down = np.round(descriptor[medida+'_down']['std'],precision)
    graf_stdDown.axhline(std_down,label='std '+str(std_down),
                         color='brown')
    for j in range(0,m,1):
        k = str(movAvg_cual[j])
        graf_stdDown.plot(tabla[condicion_down]['publishedAt'],
                          movAvgData['movAvg_down_std'][j],
                          label='movAvg_'+k,color=movAvg_color[j])
    graf_stdDown.set_ylim(y_min,y_max)
    graf_stdDown.set_xlabel('fecha')
    graf_stdDown.set_ylabel('std_down ('+medida_unidad+')',
                            color='brown')
    graf_stdDown.legend()
    graf_stdDown.grid(True,linestyle='dotted',
                      axis='x', which='both')
    plt.tight_layout()
    return(fig_std)

fig_serie = graf_puntos_serie(tabla,descriptor,movAvgData,grafData)
fig_pmf   = graf_puntos_pmf(pmf_punto,descriptor,grafData)
fig_std   = graf_puntos_std(tabla,descriptor,movAvgData,grafData)

if guarda==True:
    unarchivo = carpeta_rsm+'/serie_'+codigopunto+'.png'
    fig_serie.savefig(unarchivo)
    unarchivo = carpeta_rsm+'/pmf_'+codigopunto+'.png'
    fig_pmf.savefig(unarchivo)
    unarchivo = carpeta_rsm+'/std_'+codigopunto+'.png'
    fig_std.savefig(unarchivo)
plt.show()

Referencia: pmf, cdf en una señal de sonido. http://blog.espol.edu.ec/estg1003/senal-de-sonido-pmf-cdf/