Подтвердить что ты не робот

Логарифмический график кумулятивной функции распределения в matplotlib

У меня есть файл, содержащий зарегистрированные события. Каждая запись имеет время и латентность. Я заинтересован в построении кумулятивной функции распределения задержек. Меня больше всего интересуют задержки хвоста, поэтому я хочу, чтобы график имел логарифмическую ось y. Меня интересуют задержки в следующих процентилях: 90-е, 99-е, 99,9-е, 99,99-е и 99,999-е. Вот мой код до сих пор, который генерирует обычный график CDF:

# 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

Я знаю, на что я хочу, чтобы этот сюжет выглядел, но я изо всех сил пытался его получить. Я хочу, чтобы это выглядело (я не создал этот сюжет):

Logarithmic CDF Plot

Сделать логарифмическую ось x просто. Ось Y - это проблема, которая дает мне проблемы. Использование set_yscale('log') не работает, потому что оно хочет использовать полномочия 10. Я действительно хочу, чтобы ось y имела те же метки, что и этот график.

Как я могу получить мои данные в логарифмический график, подобный этому?

EDIT:

Если я установил yscale в 'log' и ylim до [0,1, 1], я получу следующий график:

enter image description here

Проблема заключается в том, что типичный график масштаба шкалы в наборе данных в диапазоне от 0 до 1 будет фокусироваться на значениях, близких к нулю. Вместо этого я хочу сосредоточиться на значениях, близких к 1.

4b9b3361

Ответ 1

По существу вам необходимо применить следующее преобразование к вашим значениям Y: -log10(1-y). Это налагает единственное ограничение, что y < 1, поэтому вы должны иметь отрицательные значения на преобразованном графике.

Здесь приведена измененная example из matplotlib документация, в которой показано, как включить пользовательские преобразования в "шкалы":

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()

нормальный и преобразованный график

Обратите внимание, что вы можете управлять числом 9 с помощью аргумента ключевого слова:

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

сюжет с тремя девятью

Ответ 2

Хорошо, это не самый чистый код, но я не вижу пути вокруг него. Возможно, то, что я действительно прошу, это не логарифмический CDF, но я буду ждать, пока статистик скажет мне об этом иначе. Во всяком случае, вот что я придумал:

# 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()

В грязной части я меняю ярлыки. Переменная logcdfy будет содержать значения от 0 до 10, а в моем примере она находилась между 0 и 6. В этом коде я меняю метки с процентилями. Можно также использовать функцию plot, но мне нравится, как функция scatter показывает выбросы на хвосте. Кроме того, я решил не делать ось X в масштабе журнала, потому что у моих конкретных данных есть хорошая линейная линия без нее.

enter image description here