Is it possible to draw a square matplotlib box based on percentile values โ€‹โ€‹instead of the original inputs?

From what I see, the boxplot() method expects a sequence of source values โ€‹โ€‹(numbers) as input, from which it then computes the percentiles to draw boxplot (s).

I would like to have a method with which I could pass percentiles and get the corresponding boxplot .

For instance:

Suppose I performed several tests, and for each test I measured the delays (floating point values). In addition, I previously calculated percentiles for these values.

Therefore, for each standard, I have the 25th, 50th, 75th percentile, as well as min and max.

Now, given this data, I would like to draw graphs of test boxes.

+5
source share
3 answers

To draw a graph using only percentile values โ€‹โ€‹and outliers (if any), I made a customized_box_plot function that basically modifies the attributes in the base graph (generated from tiny sample data) so that it matches your percentile values.

customized_box_plot function

 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 

USING

Using the inverse logic (the code at the very end), I extracted the percentile values โ€‹โ€‹from this 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) 

Note that to maintain this brevity, I did not show the emission vectors, which will be the 6th element of each of the percentile array.

Also note that all the usual extra kwargs / args can be used, as they are simply passed to the boxplot method inside it:

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

EXPLANATION

The boxplot method returns a dictionary matching the boxplot components with the individual matplotlib.lines.Line2D instances that were created.

Quote from matplotlib.pyplot.boxplot documentation:

This dictionary has the following keys (subject to vertical boxes):

: main body of the box showing quartiles and average confidence intervals, if included.

medians: horizontal lines in the median of each window.

whiskey: vertical lines extending to the most extreme, n-outlier data points. caps: horizontal lines at the ends of the whiskers.

fliers: points representing data that extend beyond the mustache (emissions).

means: dots or lines representing means.

For example, observe the boxplot tiny sample data [-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

The matplotlib.lines.Line2D objects have two methods that I will widely use in my function. set_xdata (or set_ydata ) and get_xdata (or get_ydata ).

Using these methods, we can reposition the component lines based on the base field to fit your percentiles (which is what the customized_box_plot function does). After changing the position of the composite lines, you can redraw the canvas using figure.canvas.draw()

Generalization of mappings from percentile to the coordinates of various Line2D objects.

Y coordinates:

  • The maximum ( q4_end - end of the 4th quartile) corresponds to the highest maximum Line2D object.
  • Min ( q1_start - the beginning of the 1st quartile) corresponds to the Line2D cap of Line2D .
  • The median corresponds to the average ( q3_start ) average Line2D object.
  • The two ends of the whiskers lie between the ends of the boxes and the q1_start caps ( q1_start and q2_start are the lower filiform rod; q4_start and q4_end are the upper filiform).
  • The box is an interesting shape n limited by a cap at the bottom. The extremes of line n correspond to q2_start and q4_start .

X coordinates:

  • The central coordinates x (for several box schedules, usually 1, 2, 3 ...)
  • The library automatically calculates x boundary coordinates based on the specified width.

REVERSE FUNCTION FOR RETURNING PERCENTIONS FROM DICT COLLECTOR:

 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 

Note: The reason that I did not create my own boxplot method is because there are many functions offered by the built-in graph that cannot be fully reproduced.

Also excuse me if I may have unnecessarily explained what may have been too obvious.

+10
source

Here is an updated version of this useful program. Setting vertices directly works for both filled fields (patchArtist = True) and for unfilled fields.

 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 
+1
source

Below is a bottom-up approach when box_plot is created using matplotlib vline , Rectangle and regular plot functions

 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 

Here's what it looks like in action:

 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

0
source

Source: https://habr.com/ru/post/1207984/


All Articles