2014-11-30 22 views
5

Z tego, co widzę, metoda oczekuje ciągu nieprzetworzonych wartości (liczb) jako danych wejściowych, z których następnie wylicza percentyle, aby narysować planszę (pola).Czy można narysować matplotlib boxplot, biorąc pod uwagę wartości percentyla zamiast oryginalnych danych wejściowych?

Chciałbym mieć metodę, dzięki której mógłbym przekazać percentyle i uzyskać odpowiedni boxplot.

Na przykład:

Załóżmy, że mam uruchomić kilka punktów odniesienia i dla każdego benchmarku mam zmierzyć opóźnienia (pływających wartości punktowe). Teraz dodatkowo wykalkulowałem percentyle dla tych wartości.

Dlatego dla każdego testu porównawczego mam 25, 50., 75. percentyl wraz z min. I maks.

Dając te dane, chciałbym narysować wykresy pudełkowe dla benchmarków.

+1

Su ggestion: czy mógłbyś przedstawić problem w sposób abstrakcyjny? Oznacza to, że zamiast mówić "latencje" użyj jakiejś abstrakcji. "Zmierzyłem pewne rzeczywiste wartości, tj. Zmiennoprzecinkowe, i chciałbym obliczyć percentyle ...". – polarise

Odpowiedz

10

Aby narysować wykres w polu przy użyciu tylko wartości percentyla i wartości odstających (jeśli istnieją), wykonałem funkcję customized_box_plot, która zasadniczo modyfikuje atrybuty na podstawowym wykresie pudełkowym (wygenerowanym z niewielkich próbek danych), aby dopasować go do wartości percentyla.

customized_box_plot funkcja

def customized_box_plot(percentiles, axes, redraw = True, *args, **kwargs): 
    """ 
    Generates a customized boxplot based on the given percentile values 
    """ 

    box_plot = axes.boxplot([[-9, -4, 2, 4, 9],]*n_box, *args, **kwargs) 
    # Creates len(percentiles) no of box plots 

    min_y, max_y = float('inf'), -float('inf') 

    for box_no, (q1_start, 
       q2_start, 
       q3_start, 
       q4_start, 
       q4_end, 
       fliers_xy) in enumerate(percentiles): 

     # Lower cap 
     box_plot['caps'][2*box_no].set_ydata([q1_start, q1_start]) 
     # xdata is determined by the width of the box plot 

     # Lower whiskers 
     box_plot['whiskers'][2*box_no].set_ydata([q1_start, q2_start]) 

     # Higher cap 
     box_plot['caps'][2*box_no + 1].set_ydata([q4_end, q4_end]) 

     # Higher whiskers 
     box_plot['whiskers'][2*box_no + 1].set_ydata([q4_start, q4_end]) 

     # Box 
     box_plot['boxes'][box_no].set_ydata([q2_start, 
              q2_start, 
              q4_start, 
              q4_start, 
              q2_start]) 

     # Median 
     box_plot['medians'][box_no].set_ydata([q3_start, q3_start]) 

     # Outliers 
     if fliers_xy is not None and len(fliers_xy[0]) != 0: 
      # If outliers exist 
      box_plot['fliers'][box_no].set(xdata = fliers_xy[0], 
              ydata = fliers_xy[1]) 

      min_y = min(q1_start, min_y, fliers_xy[1].min()) 
      max_y = max(q4_end, max_y, fliers_xy[1].max()) 

     else: 
      min_y = min(q1_start, min_y) 
      max_y = max(q4_end, max_y) 

     # The y axis is rescaled to fit the new box plot completely with 10% 
     # of the maximum value at both ends 
     axes.set_ylim([min_y*1.1, max_y*1.1]) 

    # If redraw is set to true, the canvas is updated. 
    if redraw: 
     ax.figure.canvas.draw() 

    return box_plot 

ZASTOSOWANIE

pomocą logiki odwrotnej (kod na samym końcu) I ekstrahuje wartości percentyla z tego example

>>> percentiles 
(-1.0597368367634488, 0.3977683984966961, 1.0298955252405229, 1.6693981537742526, 3.4951447843464449) 
(-0.90494930553559483, 0.36916539612108634, 1.0303658700697103, 1.6874542731392828, 3.4951447843464449) 
(0.13744105279440233, 1.3300645202649739, 2.6131540656339483, 4.8763411136047647, 9.5751914834437937) 
(0.22786243898199182, 1.4120860286080519, 2.637650402506837, 4.9067126578493259, 9.4660357513550899) 
(0.0064696168078617741, 0.30586770128093388, 0.70774153557312702, 1.5241965711101928, 3.3092932063051976) 
(0.007009744579241136, 0.28627373934008982, 0.66039691869500572, 1.4772725266672091, 3.221716765477217) 
(-2.2621660374110544, 5.1901313713883352, 7.7178532139979357, 11.277744848353247, 20.155971739152388) 
(-2.2621660374110544, 5.1884411864079532, 7.3357079047721054, 10.792299385806913, 18.842012119715388) 
(2.5417888074435702, 5.885996170695587, 7.7271286220368598, 8.9207423361593179, 10.846938621419374) 
(2.5971767318505856, 5.753551925927133, 7.6569980004033464, 8.8161056254143233, 10.846938621419374) 

Należy zauważyć, że aby to krótko nie pokazałem o wektory utliers, które będą szóstym elementem każdej z tablic percentyla.

Należy również pamiętać, że wszystkie zwykłe dodatkowe kwargs/args może być stosowany, ponieważ są one po prostu przekazywane do metody boxplot wewnątrz niego:

>>> fig, ax = plt.subplots() 
>>> b = customized_box_plot(percentiles, ax, redraw=True, notch=0, sym='+', vert=1, whis=1.5) 
>>> plt.show() 

Box Plot using Percentile values

wyjaśnienie

Metoda boxplot zwraca słownik mapujący komponenty boxplot na poszczególne instancje matplotlib.lines.Line2D zostały stworzone.

Cytując z dokumentacji matplotlib.pyplot.boxplot:

Ten słownik zawiera następujące klucze (zakładając pionowe boxplots):

pudełka: główny korpus boxplot przedstawiający kwartyle i przedziały ufności mediana, jeżeli włączona .

mediany: linie horyzontalne na środkowej stronie każdego pola.

wąsy: pionowe linie rozciągające się do najbardziej ekstremalnych, n-mniejszych punktów danych. czapki: poziome linie na końcach wąsów.

ulotki: punkty reprezentujące dane wykraczające poza wąsy (wartości odstające).

oznacza: punkty lub linie reprezentujące środki.

Na przykład obserwować boxplot z maleńkim danych próbka [-9, -4, 2, 4, 9]

>>> b = ax.boxplot([[-9, -4, 2, 4, 9],]) 
>>> b 
{'boxes': [<matplotlib.lines.Line2D at 0x7fe1f5b21350>], 
'caps': [<matplotlib.lines.Line2D at 0x7fe1f54d4e50>, 
<matplotlib.lines.Line2D at 0x7fe1f54d0e50>], 
'fliers': [<matplotlib.lines.Line2D at 0x7fe1f5b317d0>], 
'means': [], 
'medians': [<matplotlib.lines.Line2D at 0x7fe1f63549d0>], 
'whiskers': [<matplotlib.lines.Line2D at 0x7fe1f5b22e10>, 
      <matplotlib.lines.Line2D at 0x7fe20c54a510>]} 

>>> plt.show() 

A Sample box plot

W matplotlib.lines.Line2D obiekty mają dwie metody, które będę w moim użyciu funkcji obszernie. set_xdata (lub set_ydata) i get_xdata (lub).

Korzystając z tych metod, możemy zmienić położenie linii składowych wykresu skrzynki bazowej, aby dostosować je do wartości percentyla (co robi funkcja customized_box_plot). Po zmieniając pozycję Linie składnika, można przerysować płótno używając figure.canvas.draw()

Podsumowując mapowania z percentyla do współrzędnych różnych Line2D obiektów.

współrzędne Y:

  • Maksymalna (q4_end - koniec 4 kwartyl) odpowiada skrajnym górnym pokrywy Line2D obiektu.
  • Min (q1_start - początek pierwszego kwartylu) odpowiada najniższemu z najwyższych limitów obiektów Line2D.
  • Mediana odpowiada medianie (q3_start) obiektu Line2D.
  • W 2 wąsy znajdować się pomiędzy końcami skrzyń i skrajne kapsle (q1_start i q2_start - niższe bokobrody; q4_start i q4_end - górna whisker)
  • pudełko jest rzeczywiście interesujące n kształcie linii ograniczony kołpakiem w dolną część. Ekstremalne linie w kształcie n odpowiadają q2_start i q4_start.

X Współrzędne

  • Centralny współrzędne x (dla wielu wykresów skrzynkowych są zazwyczaj 1, 2, 3 ...)
  • Biblioteka automatycznie oblicza współrzędne x ograniczająca oparta na określona szerokość.

funkcja odwrotna do pobierania percentyla OD DICT boxplot:

def get_percentiles_from_box_plots(bp): 
    percentiles = [] 
    for i in range(len(bp['boxes'])): 
     percentiles.append((bp['caps'][2*i].get_ydata()[0], 
          bp['boxes'][i].get_ydata()[0], 
          bp['medians'][i].get_ydata()[0], 
          bp['boxes'][i].get_ydata()[2], 
          bp['caps'][2*i + 1].get_ydata()[0], 
          (bp['fliers'][i].get_xdata(), 
          bp['fliers'][i].get_ydata()))) 
    return percentiles 

UWAGA: Powodem, dlaczego nie zrobić to metoda całkowicie niestandardowe boxplot dlatego istnieje wiele funkcji oferowanych przez wbudowany okno dialogowe, którego nie można w pełni odtworzyć.

wybaczcie Także jeśli mogę mieć niepotrzebnie wyjaśnić coś, co może być zbyt oczywiste.

+0

to wspaniała odpowiedź! bardzo wdzięczny. dziękuję –

+0

Doskonała odpowiedź. Dziękuję Ci bardzo. – yoni

+0

trzy małe problemy napotkane w tym: (1) n_box nie jest określona (to jest łatwe ...) (2), jeśli chcesz przekazać dane percentylowe bez ulotek, pętla nie powiedzie się (lepiej pisać dla box_no, pData w enumerate (percentyle), a następnie sprawdź len pdata (3) procedura nie powiedzie się, jeśli używasz patch_artist = True (brak metody set_ydata) – maschu

1

Oto zaktualizowana wersja tej przydatnej procedury. Ustawienie wierzchołków bezpośrednio wydaje się działać dla obu wypełnionych pól (patchArtist = True) i niewypełnionych.

def customized_box_plot(percentiles, axes, redraw = True, *args, **kwargs): 
    """ 
    Generates a customized boxplot based on the given percentile values 
    """ 
    n_box = len(percentiles) 
    box_plot = axes.boxplot([[-9, -4, 2, 4, 9],]*n_box, *args, **kwargs) 
    # Creates len(percentiles) no of box plots 

    min_y, max_y = float('inf'), -float('inf') 

    for box_no, pdata in enumerate(percentiles): 
     if len(pdata) == 6: 
      (q1_start, q2_start, q3_start, q4_start, q4_end, fliers_xy) = pdata 
     elif len(pdata) == 5: 
      (q1_start, q2_start, q3_start, q4_start, q4_end) = pdata 
      fliers_xy = None 
     else: 
      raise ValueError("Percentile arrays for customized_box_plot must have either 5 or 6 values") 

     # Lower cap 
     box_plot['caps'][2*box_no].set_ydata([q1_start, q1_start]) 
     # xdata is determined by the width of the box plot 

     # Lower whiskers 
     box_plot['whiskers'][2*box_no].set_ydata([q1_start, q2_start]) 

     # Higher cap 
     box_plot['caps'][2*box_no + 1].set_ydata([q4_end, q4_end]) 

     # Higher whiskers 
     box_plot['whiskers'][2*box_no + 1].set_ydata([q4_start, q4_end]) 

     # Box 
     path = box_plot['boxes'][box_no].get_path() 
     path.vertices[0][1] = q2_start 
     path.vertices[1][1] = q2_start 
     path.vertices[2][1] = q4_start 
     path.vertices[3][1] = q4_start 
     path.vertices[4][1] = q2_start 

     # Median 
     box_plot['medians'][box_no].set_ydata([q3_start, q3_start]) 

     # Outliers 
     if fliers_xy is not None and len(fliers_xy[0]) != 0: 
      # If outliers exist 
      box_plot['fliers'][box_no].set(xdata = fliers_xy[0], 
              ydata = fliers_xy[1]) 

      min_y = min(q1_start, min_y, fliers_xy[1].min()) 
      max_y = max(q4_end, max_y, fliers_xy[1].max()) 

     else: 
      min_y = min(q1_start, min_y) 
      max_y = max(q4_end, max_y) 

     # The y axis is rescaled to fit the new box plot completely with 10% 
     # of the maximum value at both ends 
     axes.set_ylim([min_y*1.1, max_y*1.1]) 

    # If redraw is set to true, the canvas is updated. 
    if redraw: 
     ax.figure.canvas.draw() 

    return box_plot 
+0

dziękuję Jeśli ktoś zastanawia się, jak przypisać etykiety do boxplots [this] (https://stackoverflow.com/a/43460149/4155911) odpowiedź wskazuje to dobrze tl; dr 'ax.set_xticklabels (x_ticks_labels obrót = 'pionowy', fontsize = 18)' – Rotkiv

0

Oto podejście oddolne gdzie box_plot jest zbudowany przy użyciu matplotlib na vline, Rectangle i normalne plot funkcje

def boxplot(df, ax=None, box_width=0.2, whisker_size=20, mean_size=10, median_size = 10 , line_width=1.5, xoffset=0, 
        color=0): 
    """Plots a boxplot from existing percentiles. 

    Parameters 
    ---------- 
    df: pandas DataFrame 
    ax: pandas AxesSubplot 
     if to plot on en existing axes 
    box_width: float 
    whisker_size: float 
     size of the bar at the end of each whisker 
    mean_size: float 
     size of the mean symbol 
    color: int or rgb(list) 
     If int particular color of property cycler is taken. Example of rgb: [1,0,0] (red) 

    Returns 
    ------- 
    f, a, boxes, vlines, whisker_tips, mean, median 
    """ 

    if type(color) == int: 
     color = plt.rcParams['axes.prop_cycle'].by_key()['color'][color] 

    if ax: 
     a = ax 
     f = a.get_figure() 
    else: 
     f, a = plt.subplots() 

    boxes = [] 
    vlines = [] 
    xn = [] 
    for row in df.iterrows(): 
     x = row[0] + xoffset 
     xn.append(x) 

     # box 
     y = row[1][25] 
     height = row[1][75] - row[1][25] 
     box = plt.Rectangle((x - box_width/2, y), box_width, height) 
     a.add_patch(box) 
     boxes.append(box) 

     # whiskers 
     y = (row[1][95] + row[1][5])/2 
     vl = a.vlines(x, row[1][5], row[1][95]) 
     vlines.append(vl) 

    for b in boxes: 
     b.set_linewidth(line_width) 
     b.set_facecolor([1, 1, 1, 1]) 
     b.set_edgecolor(color) 
     b.set_zorder(2) 

    for vl in vlines: 
     vl.set_color(color) 
     vl.set_linewidth(line_width) 
     vl.set_zorder(1) 

    whisker_tips = [] 
    if whisker_size: 
     g, = a.plot(xn, df[5], ls='') 
     whisker_tips.append(g) 

     g, = a.plot(xn, df[95], ls='') 
     whisker_tips.append(g) 

    for wt in whisker_tips: 
     wt.set_markeredgewidth(line_width) 
     wt.set_color(color) 
     wt.set_markersize(whisker_size) 
     wt.set_marker('_') 

    mean = None 
    if mean_size: 
     g, = a.plot(xn, df['mean'], ls='') 
     g.set_marker('o') 
     g.set_markersize(mean_size) 
     g.set_zorder(20) 
     g.set_markerfacecolor('None') 
     g.set_markeredgewidth(line_width) 
     g.set_markeredgecolor(color) 
     mean = g 

    median = None 
    if median_size: 
     g, = a.plot(xn, df['median'], ls='') 
     g.set_marker('_') 
     g.set_markersize(median_size) 
     g.set_zorder(20) 
     g.set_markeredgewidth(line_width) 
     g.set_markeredgecolor(color) 
     median = g 

    a.set_ylim(np.nanmin(df), np.nanmax(df)) 
    return f, a, boxes, vlines, whisker_tips, mean, median 

Jak to wygląda w akcji:

import numpy as np 
import pandas as pd 
import matplotlib.pylab as plt 

nopts = 12 
df = pd.DataFrame() 
df['mean'] = np.random.random(nopts) + 7 
df['median'] = np.random.random(nopts) + 7 
df[5] = np.random.random(nopts) + 4 
df[25] = np.random.random(nopts) + 6 
df[75] = np.random.random(nopts) + 8 
df[95] = np.random.random(nopts) + 10 
out = boxplot(df) 

enter image description here