How to turn seaborn boxplot fliers on/off with buttons - matplotlib

I want to implement buttons to turn on/off the fliers in a set of seaborn boxplots. I tried to follow the method of changing through the artists mentioned in this link: https://stackoverflow.com/a/36893152/18193150 but was unsuccessful. Appreciate if someone can show me how to do it. Cheers.
This is the code I tried with:
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
from matplotlib.widgets import Button
x = np.arange(1, 13)
index = np.repeat(x, 40)
np.random.seed(123)
df = pd.DataFrame({'A': np.random.normal(30, 2, len(index)),
'B': np.random.normal(10, 2, len(index))},
index=index)
red_diamond = dict(markerfacecolor='r', marker='D')
blue_dot = dict(markerfacecolor='b', marker='o')
fig=plt.figure(figsize=[10, 5])
ax = sns.boxplot(data=df, x=df.index, y='A', width=0.5, color='red',
boxprops=dict(alpha=.5, label='A'), flierprops=red_diamond)
sns.boxplot(data=df, x=df.index, y='B', width=0.5, color='blue',
boxprops=dict(alpha=.5, label='B'), flierprops=blue_dot, ax=ax)
# button to off boxplot fliers
resetax_off = plt.axes([0.8, 0.02, 0.08, 0.035])
button_off = Button(resetax_off, 'Flier off', color='red',
hovercolor='lightslategrey')
# button to on boxplot fliers
resetax_on = plt.axes([0.6, 0.02, 0.08, 0.035])
button_on = Button(resetax_on, 'Flier on', color='gold',
hovercolor='lightslategrey')
def click_off(event):
for i,artist in enumerate(ax.artists):
line = ax1.line[i+4] #trying to get Line2D for the fliers, 4th in the list of 6
line.set(alpha=0)
fig.canvas.draw_idle()
button_off.on_clicked(click_off)
def click_on(event):
for i,artist in enumerate(ax.artists):
line = ax1.line[i+4] #trying to get Line2D for the fliers
line.set(alpha=1)
fig.canvas.draw_idle()
button_on.on_clicked(click_on)
plt.show()

Related

How to add labels to sets of seaborn boxplot

I have 2 sets of boxplots, one set in blue color and another in red color. I want the legend to show the label for each set of boxplots, i.e.
Legend:
-blue box- A, -red box- B
Added labels='A' and labels='B' within sns.boxplot(), but didn't work with error message "No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument". How do I add the labels?
enter image description here
code for the inserted image:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
x = list(range(1,13))
n = 40
index = [item for item in x for i in range(n)]
np.random.seed(123)
df = pd.DataFrame({'A': np.random.normal(30, 2, len(index)),
'B': np.random.normal(10, 2, len(index))},
index=index)
red_diamond = dict(markerfacecolor='r', marker='D')
blue_dot = dict(markerfacecolor='b', marker='o')
plt.figure(figsize=[10,5])
ax = plt.gca()
ax1 = sns.boxplot( x=df.index, y=df['A'], width=0.5, color='red', \
boxprops=dict(alpha=.5), flierprops=red_diamond, labels='A')
ax2 = sns.boxplot( x=df.index, y=df['B'], width=0.5, color='blue', \
boxprops=dict(alpha=.5), flierprops=blue_dot, labels='B')
plt.ylabel('Something')
plt.legend(loc="center", fontsize=8, frameon=False)
plt.show()
Here are the software versions I am using: seaborn version 0.11.2. matplotlib version 3.5.1. python version 3.10.1
The following approach sets a label via the boxprops, and creates a legend using part of ax.artists. (Note that ax, ax1 and ax2 of the question's code are all pointing to the same subplot, so here only ax is used.)
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
x = np.arange(1, 13)
index = np.repeat(x, 40)
np.random.seed(123)
df = pd.DataFrame({'A': np.random.normal(30, 2, len(index)),
'B': np.random.normal(10, 2, len(index))},
index=index)
red_diamond = dict(markerfacecolor='r', marker='D')
blue_dot = dict(markerfacecolor='b', marker='o')
plt.figure(figsize=[10, 5])
ax = sns.boxplot(data=df, x=df.index, y='A', width=0.5, color='red',
boxprops=dict(alpha=.5, label='A'), flierprops=red_diamond)
sns.boxplot(data=df, x=df.index, y='B', width=0.5, color='blue',
boxprops=dict(alpha=.5, label='B'), flierprops=blue_dot, ax=ax)
ax.set_ylabel('Something')
handles, labels = ax.get_legend_handles_labels()
handles = [h for h, lbl, prev in zip(handles, labels, [None] + labels) if lbl != prev]
ax.legend(handles=handles, loc="center", fontsize=8, frameon=False)
plt.show()
Alternative approaches could be:
pd.melt the dataframe to long form, so hue could be used; a problem here is that then the legend wouldn't take the alpha from the boxprops into account; also setting different fliers wouldn't be supported
create a legend from custom handles

How to rotate a Contextily basemap in matplotlib and Jupyter notebook

I am making a set of figures with subplots in Jupyter Notebook using matplotlib and geopandas. The top plots (A & B) have geospatial data and use various basemaps (aerial imagery, shaded relief, etc.).
How can I rotate the top two plots 90-degrees, so that they are elongated?
(I will need to redo gridspec layout of course, but that is easy; what I don't know how to do is: rotate the plots but keep the geographic information for basemap plotting.)
Repeatable code is below.
import pandas as pd
import geopandas as gpd
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import contextily as ctx
from shapely.geometry import Point
plt.style.use('seaborn-whitegrid')
### DUMMY DATA
long, lat = [(-118.155, -118.051, -118.08), (38.89, 39.512, 39.1)]
q, t = [(0, 70500, 21000), (0, 8000, -1200)]
df = pd.DataFrame(list(zip(q, t, lat, long)), columns =['q', 't', 'lat', 'long'])
gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df['long'], df['lat']))
gdf.crs = "EPSG:4326"
### PLOTTING
fig = plt.figure(figsize=(10,7.5), constrained_layout=True)
gs = fig.add_gridspec(3, 2)
ax1 = fig.add_subplot(gs[0:2, 0])
ax2 = fig.add_subplot(gs[0:2, 1], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[-1, :])
### PlotA
gdf.plot(ax = ax1)
ctx.add_basemap(ax1, crs='epsg:4326', source=ctx.providers.Esri.WorldShadedRelief)
ax1.set_aspect('equal')
ax1.set_title('Plot-A')
ax1.tick_params('x', labelrotation=90)
### PlotB
gdf.plot(ax = ax2)
ctx.add_basemap(ax2, crs='epsg:4326', source=ctx.providers.Esri.WorldImagery, alpha=0.5)
ax2.set_aspect('equal')
ax2.set_title('Plot-B')
ax2.tick_params('x', labelrotation=90)
### PlotC
ax3.scatter(df.q, df.t)
ax3.set_aspect('equal')
ax3.set_title('Plot-C')
ax3.set_xlabel('q')
ax3.set_ylabel('t')

pandas plot multiple df in canvas

I have difficulty with setting two different data plots inside one axis in tkinter canvas. Currently is being displayed only last plot, second is hidden.
Update:
Test example is working, but not in my original setup.
Problem is moved here.
Test example:
from tkinter import Tk, Canvas
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
window = Tk()
window.geometry('800x600')
df = pd.DataFrame()
df['x'] = np.linspace(1, 10, 10)
df['y'] = np.random.randint(1, 10, 10)
df2 = pd.DataFrame()
df2['x'] = np.linspace(1, 10, 50)
df2['y'] = np.random.randint(1, 10, 50)
fig, ax = plt.subplots()
df.plot(x='x', y='y', kind='bar', ax=ax, width=1., figsize=(3, 2.5), legend=None)
df2.plot(x='x', y='y', kind='line', ax=ax, legend=None)
Canvas(window, background='white') # create canvas field
canvas_plot = FigureCanvasTkAgg(fig, window) # Draw area
canvas_plot.get_tk_widget().grid(column=1, row=6, padx=1, pady=10, rowspan=2, columnspan=3)
canvas_plot.draw() # draw canvas
window.mainloop()
Question:
How to make this plots to be displayed in one figure?

Seaborn boxplot custom lables aside box

I have the code segment given below, and it generates the provided boxplot. I would like to know how to add custom labels aside each box, so that the boxplot is even more digestible to the readers of my result. The expected diagram is also provided. I reckon there should be an easy way to get this done in Seaborn/Matplotlib.
What I exactly want is to add the following labels to each box (on left hand side as in shown in the example provided)
The code use to generate boxplot
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as MaxNLocator
from matplotlib import rcParams
from matplotlib.ticker import ScalarFormatter, FuncFormatter,FormatStrFormatter, EngFormatter#, mticker
%matplotlib inline
import seaborn as sns
range_stats = pd.read_csv(f'{snappy_data_dir}range_searcg_snappy_stats.csv')
data_stats_rs_txt = range_stats[range_stats['category'] == "t"]
data_stats_rs_seq = range_stats[range_stats['category'] == "s"]
fig, ax =plt.subplots(1,2)
rcParams['figure.figsize'] =8, 6
flierprops = dict(marker='x')
labels1 = ['R1', 'R2', 'R3', 'R4', 'R5']
sns.boxplot(x='Interval',y='Total',data=data_stats_rs_txt,palette='rainbow', ax=ax[0])
sns.boxplot(x='Interval',y='Total',data=data_stats_rs_seq,palette='rainbow', ax=ax[1])
ax[0].set(xlabel='Interval (s)', ylabel='query execution time (s)', title='Text format', ylim=(0, 290))
ax[1].set(xlabel='Interval (s)', ylabel='', title='Proposed format',ylim=(0, 290), yticklabels=[])
plt.savefig("range-query-corrected.svg")
plt.savefig('snappy_compressed_rangesearch.pdf')
Resulted figure:
Expected figure with labels
This might help you, although it is not a fully correct way and is not a complete solution.
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
tips = sns.load_dataset('tips')
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
sns.set_context('poster',font_scale=0.5)
sns.boxplot(x="day", y="total_bill", data=tips,palette='rainbow', ax=axes[0], zorder=0)
axes[0].text(0, 45, r"$B1$", fontsize=20, color="blue")
axes[0].text(0.9, 45, r"$B2$", fontsize=20, color="blue")
axes[0].text(2.2, 45, r"$B3$", fontsize=20, color="blue")
axes[0].text(3.1, 45, r"$B4$", fontsize=20, color="blue");
sns.boxplot(x="day", y="tip", data=tips,palette='rainbow', ax=axes[1], zorder=10)
iris = sns.load_dataset("iris")
x_var = 'species'
y_var = 'sepal_width'
x_order = ['setosa', 'versicolor', 'virginica']
labels = ['R1','R2','R3']
max_vals = iris.groupby(x_var).max()[y_var].reindex(x_order)
ax = sns.boxplot(x=x_var, y=y_var, data=iris)
for x,y,l in zip(range(len(x_order)), max_vals, labels):
ax.annotate(l, xy=[x,y], xytext=[0,5], textcoords='offset pixels', ha='center', va='bottom')

add_axes with no frame

I'm trying to insert an axis inside a plot but without the frame. Somehow the options do not work at all
I have the following mwe
import matplotlib.pyplot as plt
import mpl_toolkits.axisartist as AA
f = plt.figure()
ax = f.add_subplot(111)
iax = AA.Axes(f, [0.125, 0.125, 0.775, 0.2], frameon=False)
f.add_axes(iax, frameon=False)
iax.set_yticks([]), iax.set_xticks([])
plt.show()
The purpose of using matplotlib.axisartist is not clear from the question. To obtain a frameless axes you may use figure.add_axes([..], frameon=False):
import matplotlib.pyplot as plt
f = plt.figure()
ax = f.add_subplot(111)
iax = f.add_axes([0.125, 0.125, 0.775, 0.2], frameon=False)
iax.set_yticks([]), iax.set_xticks([])
iax.plot(range(10),[i%4 for i in range(10)])
plt.show()