2015-06-30 14 views
13

Ho un file contenente eventi registrati. Ogni voce ha un tempo e una latenza. Sono interessato a tracciare la funzione di distribuzione cumulativa delle latenze. Sono più interessato alle latenze di coda, quindi voglio che la trama abbia un asse y logaritmico. Sono interessato alle latenze ai seguenti percentili: 90 °, 99 °, 99,9 °, 99,99 ° e 99,999 °. Ecco il mio codice finora che genera una trama regolare CDF:Trama logaritmica di una funzione di distribuzione cumulativa in matplotlib

# retrieve event times and latencies from the file 
times, latencies = read_in_data_from_file('myfile.csv') 
# compute the CDF 
cdfx = numpy.sort(latencies) 
cdfy = numpy.linspace(1/len(latencies), 1.0, len(latencies)) 
# plot the CDF 
plt.plot(cdfx, cdfy) 
plt.show() 

Regular CDF Plot

So quello che voglio la trama a guardare come, ma ho lottato per ottenerlo. Voglio farlo sembrare come questo (non ho generare questa trama):

Logarithmic CDF Plot

Effettuare la logaritmica asse x è semplice. L'asse y è quello che mi dà problemi. L'uso di set_yscale('log') non funziona perché vuole usare le potenze di 10. Voglio davvero che l'asse y abbia lo stesso ticklabels di questa trama.

Come posso ottenere i miei dati in una trama logaritmica come questa?

EDIT:

Se ho impostato il yscale a 'log', e ylim a [0.1, 1], ottengo il seguente grafico:

enter image description here

Il problema è che un tipico il grafico della scala del log su un set di dati compreso tra 0 e 1 si focalizzerà su valori prossimi allo zero. Invece, voglio concentrarmi sui valori vicini a 1.

+2

Che tipo di problemi stai avendo con 'set_yscale ('symlog')'? – mziccard

+0

Anche l'impostazione delle posizioni delle etichette è una storia completamente diversa. Suppongo che tu possa fare la scala logaritmica sull'asse y (funziona, se hai un numero 0 o -ve i dati sono sbagliati) e quindi regola le etichette. –

+1

Cosa intendi quando dici che il registro asse y * "non funziona" *? Potresti mostrarci? Non è matematicamente possibile rappresentare 0 su una scala di log, quindi il primo valore dovrà essere mascherato o ritagliato su un numero positivo molto piccolo. Puoi controllare questo comportamento passando "maschera" o "clip" come parametro "nonposy =" a "ax.set_yscale()". –

risposta

14

In sostanza è necessario applicare la seguente trasformazione ai vostri Y valori: -log10(1-y). Questo impone l'unica limitazione a y < 1, quindi dovresti essere in grado di avere valori negativi sul grafico trasformato.

Ecco una versione modificata example da matplotlib documentazione che mostra come incorporare trasformazioni personalizzate in "scale":

import numpy as np 
from numpy import ma 
from matplotlib import scale as mscale 
from matplotlib import transforms as mtransforms 
from matplotlib.ticker import FixedFormatter, FixedLocator 


class CloseToOne(mscale.ScaleBase): 
    name = 'close_to_one' 

    def __init__(self, axis, **kwargs): 
     mscale.ScaleBase.__init__(self) 
     self.nines = kwargs.get('nines', 5) 

    def get_transform(self): 
     return self.Transform(self.nines) 

    def set_default_locators_and_formatters(self, axis): 
     axis.set_major_locator(FixedLocator(
       np.array([1-10**(-k) for k in range(1+self.nines)]))) 
     axis.set_major_formatter(FixedFormatter(
       [str(1-10**(-k)) for k in range(1+self.nines)])) 


    def limit_range_for_scale(self, vmin, vmax, minpos): 
     return vmin, min(1 - 10**(-self.nines), vmax) 

    class Transform(mtransforms.Transform): 
     input_dims = 1 
     output_dims = 1 
     is_separable = True 

     def __init__(self, nines): 
      mtransforms.Transform.__init__(self) 
      self.nines = nines 

     def transform_non_affine(self, a): 
      masked = ma.masked_where(a > 1-10**(-1-self.nines), a) 
      if masked.mask.any(): 
       return -ma.log10(1-a) 
      else: 
       return -np.log10(1-a) 

     def inverted(self): 
      return CloseToOne.InvertedTransform(self.nines) 

    class InvertedTransform(mtransforms.Transform): 
     input_dims = 1 
     output_dims = 1 
     is_separable = True 

     def __init__(self, nines): 
      mtransforms.Transform.__init__(self) 
      self.nines = nines 

     def transform_non_affine(self, a): 
      return 1. - 10**(-a) 

     def inverted(self): 
      return CloseToOne.Transform(self.nines) 

mscale.register_scale(CloseToOne) 

if __name__ == '__main__': 
    import pylab 
    pylab.figure(figsize=(20, 9)) 
    t = np.arange(-0.5, 1, 0.00001) 
    pylab.subplot(121) 
    pylab.plot(t) 
    pylab.subplot(122) 
    pylab.plot(t) 
    pylab.yscale('close_to_one') 

    pylab.grid(True) 
    pylab.show() 

normal and transformed plot

noti che è possibile controllare il numero di 9 di via un argomento parola chiave:

pylab.figure() 
pylab.plot(t) 
pylab.yscale('close_to_one', nines=3) 
pylab.grid(True) 

plot with 3 nine's

+0

ottima risposta. Questo e 'esattamente quello che stavo cercando. Tutto funziona come previsto tranne una cosa ... Quando provo a usare scatter() invece di plot(), non funziona (non compare nulla). Cosa devo fare per far sparire scatter()? – nic

+0

@nic Come si chiama 'scatter()'? Tutto funziona per me se sostituisco semplicemente le chiamate 'plot()' con: 'pylab.scatter (t, t)'. –

+0

hai ragione. Ho avuto un problema altrove. Grazie ancora per la tua risposta. Ne è valsa la pena +100 – nic

1

Ok, questo non è il codice più pulito, ma non riesco a vedere un modo per aggirarlo. Forse quello che sto veramente chiedendo non è un CDF logaritmico, ma aspetterò che uno statistico mi dica il contrario. Ad ogni modo, ecco cosa mi si avvicinò con:

# retrieve event times and latencies from the file 
times, latencies = read_in_data_from_file('myfile.csv') 
cdfx = numpy.sort(latencies) 
cdfy = numpy.linspace(1/len(latencies), 1.0, len(latencies)) 

# find the logarithmic CDF and ylabels 
logcdfy = [-math.log10(1.0 - (float(idx)/len(latencies))) 
      for idx in range(len(latencies))] 
labels = ['', '90', '99', '99.9', '99.99', '99.999', '99.9999', '99.99999'] 
labels = labels[0:math.ceil(max(logcdfy))+1] 

# plot the logarithmic CDF 
fig = plt.figure() 
axes = fig.add_subplot(1, 1, 1) 
axes.scatter(cdfx, logcdfy, s=4, linewidths=0) 
axes.set_xlim(min(latencies), max(latencies) * 1.01) 
axes.set_ylim(0, math.ceil(max(logcdfy))) 
axes.set_yticklabels(labels) 
plt.show() 

La parte disordinato è dove posso cambiare le yticklabels. La variabile logcdfy contiene valori compresi tra 0 e 10 e nel mio esempio era compresa tra 0 e 6. In questo codice, scambio le etichette con percentili. È anche possibile utilizzare la funzione plot ma mi piace il modo in cui la funzione scatter mostra i valori anomali sulla coda. Inoltre, ho scelto di non rendere l'asse x su una scala di registro perché i miei dati particolari hanno una buona linea lineare senza di essa.

enter image description here

+2

Stai impostando le etichette, ma non le zecche, in questo modo il numero che viene mostrato (etichetta) non corrisponde al valore del segno di spunta !!! E perché non dovresti semplicemente usare l'opzione di scalatura logaritmica predefinita di matplotlib? – hitzg

+0

@hitzg, sono d'accordo con il tuo commento. Mi dà fastidio che le etichette non corrispondano ai dati reali. Ho provato, provato e provato, ma non riesco a capire come ottenere la trama per assomigliare alla trama di cui ho bisogno senza questo trucco. Sarei molto grato se potesse mostrarmi come! Il ridimensionamento logaritmico predefinito di matplotlib non enfatizza la parte dei dati a cui tengo, ovvero i percentili della coda. – nic

Problemi correlati