title: Pandas DataFrame grouped box plot from aggregated results
body: <p>I want to draw box plot, but I don't have raw data but aggregated results in Pandas DataFrame. </p>
  
  <p>Is it still possible to draw box plot from the aggregated results? </p>
  
  <p>If not, what is the closest plot that I can get, to plot the min, max, mean, median, std-dev etc. I know I can plot them using line chart, but I need the boxplots to be grouped/clustered. </p>
  
  <p>Here is my data, the plotting part is missing. Please help. Thanks</p>
  
  <pre><code>import matplotlib.pyplot as plt
  
  import numpy as np
  import pandas as pd
  
  df = pd.DataFrame({
          'group' : ['Tick Tick Tick', 'Tock Tock Tock', 'Tock Tock Tock', 'Tick Tick Tick']*3, # , ['Tock Tock Tock', 'Tick Tick Tick']*6,
          'person':[x*5 for x in list('ABC')]*4,
          'Median':np.random.randn(12),
          'StdDev':np.random.randn(12)
                     })
  df["Average"]=df["Median"]*1.1
  df["Minimum"]=df["Median"]*0.5
  df["Maximum"]=df["Median"]*1.6
  df["90%"]=df["Maximum"]*0.9
  df["95%"]=df["Maximum"]*0.95
  df["99%"]=df["Maximum"]*0.99
  
  df
  </code></pre>
  
  <p><strong>UPDATE</strong>, </p>
  
  <p>I'm now one step closer to get my result -- I have just found that this feature was <a href="https://github.com/matplotlib/matplotlib/pull/2643" rel="nofollow noreferrer">available since matplotlib 1.4</a>, and I'm using matplotlib 1.5, and I tested it and <a href="https://gist.github.com/suntong/56490c89220592fa7409" rel="nofollow noreferrer">proved that it is working for me</a>. </p>
  
  <p>The problem is I have no clue why it works, and how to adapt my above code to use such new feature. I'll re-post my working code below, hope someone can understand and put two and two together. </p>
  
  <p>The data I have are Median, Average, Minimum, 90%,95%, 99%, Maximum and StdDev, and I hope to chart them all. and I took a look at the data structure of <code>logstats</code> of the following code, after the <code>for stats, label in zip(logstats, list('ABCD'))</code>, and found its fields are: </p>
  
  <pre><code>[{'cihi': 4.2781254505311281,
    'cilo': 1.6164348064249057,
    'fliers': array([ 19.69118642,  19.01171604]),
    'iqr': 5.1561885723613567,
    'label': 'A',
    'mean': 4.9486856766955922,
    'med': 2.9472801284780168,
    'q1': 1.7655440553898782,
    'q3': 6.9217326277512345,
    'whishi': 12.576334012545718,
    'whislo': 0.24252084924003742},
   {'cihi': 4.3186289184254107,
    'cilo': 1.9963715983778565,
    ...
  </code></pre>
  
  <p>So, from this</p>
  
  <p><a href="https://i.stack.imgur.com/AJDKf.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/AJDKf.png" alt="box plot"></a></p>
  
  <p>and the <code>bxp</code> doc, I'm going to map my data as follows:</p>
  
  <ul>
  <li>whislo: Minimum </li>
  <li>q1: Median</li>
  <li>med: Average</li>
  <li>mean: 90%</li>
  <li>q3: 95%</li>
  <li>whishi: 99%</li>
  <li>and Maximum as fliers</li>
  </ul>
  
  <p>To map them, I'll just do <code>SELECT Minimum AS whislo, [90%] AS mean, [95%] as q3, [99%] as whishi</code>...Here is the final result:</p>
  
  <pre><code>raw_data = {'label': ['Label_01 Init', 'Label_02', 'Label_03', 'Label_04', 'Label_05', 'Label_06', 'Label_07', 'Label_08', 'Label_99'], 'whislo': [0.17999999999999999, 2.0299999999999998, 4.0800000000000001, 2.0899999999999999, 2.3300000000000001, 2.3799999999999999, 1.97, 2.6499999999999999, 0.089999999999999997], 'q3': [0.5, 4.9699999999999998, 11.77, 5.71, 12.460000000000001, 11.859999999999999, 13.84, 16.969999999999999, 0.29999999999999999], 'mean': [0.40000000000000002, 4.1299999999999999, 10.619999999999999, 5.0999999999999996, 10.24, 9.0700000000000003, 11.960000000000001, 15.15, 0.26000000000000001], 'whishi': [1.76, 7.6399999999999997, 20.039999999999999, 6.6699999999999999, 22.460000000000001, 21.66, 16.629999999999999, 19.690000000000001, 1.1799999999999999], 'q1': [0.28000000000000003, 2.96, 7.6100000000000003, 3.46, 5.8099999999999996, 5.4400000000000004, 6.6299999999999999, 8.9900000000000002, 0.16], 'fliers': [5.5, 17.129999999999999, 32.890000000000001, 7.9100000000000001, 32.829999999999998, 70.680000000000007, 24.699999999999999, 32.240000000000002, 3.3500000000000001]}
  df = pd.DataFrame(raw_data, columns = ['label', 'whislo', 'q1', 'mean', 'q3', 'whishi', 'fliers'])
  </code></pre>
  
  <p>Now to challenge is how to present my above dataframe in box plot with multiple level of grouping. If multiple level of grouping is too difficult, let's get the plotting from pd dataframe working first, because my <code>pd</code> dataframe has the same fields as the required <code>np</code> array. So I tried,</p>
  
  <pre><code>fig, ax = plt.subplots()
  ax.bxp(df.as_matrix(), showmeans=True, showfliers=True, vert=False)
  </code></pre>
  
  <p>But I got</p>
  
  <pre><code>...\Anaconda3\lib\site-packages\matplotlib\axes\_axes.py in bxp(self, bxpstats, positions, widths, vert, patch_artist, shownotches, showmeans, showcaps, showbox, showfliers, boxprops, whiskerprops, flierprops, medianprops, capprops, meanprops, meanline, manage_xticks)
     3601         for pos, width, stats in zip(positions, widths, bxpstats):
     3602             # try to find a new label
  -&gt; 3603             datalabels.append(stats.get('label', pos))
     3604             # fliers coords
     3605             flier_x = np.ones(len(stats['fliers'])) * pos
  
  AttributeError: 'numpy.ndarray' object has no attribute 'get'
  </code></pre>
  
  <p>If I use <code>ax.bxp(df.to_records(), ...</code>, then I'll get <code>AttributeError: 'record' object has no attribute 'get'</code>.</p>
  
  <p>OK, I finally got it working, the plotting from pd dataframe, but not multiple level of grouping, like this:</p>
  
  <pre><code>df['fliers']=''
  fig, ax = plt.subplots()
  ax.bxp(df.to_dict('records'), showmeans=True, meanline=True, showfliers=False, vert=False) # shownotches=True, 
  plt.show()
  </code></pre>
  
  <p>Note my above data is missing the <code>med</code> field, you can add the correct ones, or use <code>df['med']=df['q1']*1.2</code> to make it works. </p>
  
  <pre><code>import matplotlib
  import matplotlib.pyplot as plt
  
  import numpy as np
  import pandas as pd
  
  def test_bxp_with_ylabels():
      np.random.seed(937)
      logstats = matplotlib.cbook.boxplot_stats(
          np.random.lognormal(mean=1.25, sigma=1., size=(37,4))
      )
      print(logstats)
      for stats, label in zip(logstats, list('ABCD')):
          stats['label'] = label
  
      fig, ax = plt.subplots()
      ax.set_xscale('log')
      ax.bxp(logstats, vert=False)
  
  test_bxp_with_ylabels()
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/cynPy.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/cynPy.png" alt="bxp_with_ylabels"></a></p>