title: How to iteratively plot different data as boxplots in seaborn (without them overlapping)?
body: <p>Is there a way to iteratively plot data using <code>seaborn</code>'s <a href="https://seaborn.pydata.org/generated/seaborn.boxplot.html" rel="nofollow noreferrer"><code>sns.boxplot()</code></a> without having the boxplots overlap? (without combining datasets into a single <a href="https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html" rel="nofollow noreferrer"><code>pd.DataFrame()</code></a>)</p>
  
  <p><strong>Background</strong></p>
  
  <p>Sometimes when comparing different (e.g. size/shape) datasets, a mutual comparison is often useful and can be made by binning the datasets by a different shared variable (via <a href="https://pandas.pydata.org/pandas-docs/stable/generated/pandas.cut.html" rel="nofollow noreferrer"><code>pd.cut()</code></a> and <a href="https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.groupby.html" rel="nofollow noreferrer"><code>df.groupby()</code></a>, as shown below). </p>
  
  <p>Previously, I have iteratively plotted these "binned" data as boxplots on the same axis  by looping separate DataFrames using <code>matplotlib</code>'s <a href="https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.boxplot.html" rel="nofollow noreferrer"><code>ax.boxplot()</code></a> (by providing y axis location values as a <code>position</code> argument to to ensure boxplots don't overlap).  </p>
  
  <p><strong>Example</strong></p>
  
  <p>Below is an simplified example that shows the overlapping plots in when using <code>sns.boxplot()</code>:</p>
  
  <pre><code>import seaborn as sns
  import random
  import pandas as pd
  import matplotlib.pyplot as plt
  
  # Get the tips dataset and select a subset as an example
  tips = sns.load_dataset("tips")
  variable_to_bin_by = 'tip'
  binned_variable = 'total_bill'
  df = tips[[binned_variable,  variable_to_bin_by] ]
  
  # Create a second dataframe with different values and shape
  df2 = pd.concat( [ df.copy() ] *5 )    
  # Use psuedo random numbers to convey that df2 is different to df
  scale = [ random.uniform(0,2) for i in range(len(df2[binned_variable])) ]  
  df2[ binned_variable ] = df2[binned_variable].values * scale * 5
  dfs = [ df, df2 ]
  
  # Group the data by a list of bins
  bins = [0, 1, 2, 3, 4]
  for n, df in enumerate( dfs ):
      gdf = df.groupby( pd.cut(df[variable_to_bin_by].values, bins ) )
      data = [ i[1][binned_variable].values  for i in gdf]
      dfs[n] = pd.DataFrame( data, index = bins[:-1])
  
  # Create an axis for both DataFrames to be plotted on
  fig, ax = plt.subplots()
  
  # Loop the DataFrames and plot
  colors = ['red', 'black']    
  for n in range(2):
      ax = sns.boxplot( data=dfs[n].T, ax=ax, width=0.2, orient='h', 
                        color=colors[n] )
  plt.ylabel( variable_to_bin_by )
  plt.xlabel( binned_variable )
  plt.show()
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/JhlYM.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/JhlYM.png" alt="Over lapping boxplots"></a></p>
  
  <p><strong>More detail</strong></p>
  
  <p>I realise the simplified example above could resolved by combining the DataFrames and providing the <code>hue</code> argument to <code>sns.boxplot()</code>. </p>
  
  <p>Updating the index of the DataFrames provide also doesn't help, as y values from the last DataFrame provided is then used. </p>
  
  <p>Providing the <code>kwargs</code> argument (e.g. <code>kwargs={'positions': dfs[n].T.index}</code>) won't work as this raises a <code>TypeError</code>.</p>
  
  <blockquote>
    <p>TypeError: boxplot() got multiple values for keyword argument
    'positions'</p>
  </blockquote>
  
  <p>The setting <code>sns.boxplot()</code>'s <code>dodge</code> argument to <code>True</code> doesn't solve this.</p>