Matplotlib shows no reading if the tick labels get modified - python

I expect to hover my mouse over the plot and get clean data reading on the right side of the navigation bar in the automatically-generated plot window.
In my case (see code at the bottom), however, if I turn the y-axis tick labels into decibel, the y-reading in the navigation bar (bottom right corner) will disappear, like this:
Workaround: If you comment out the #PROBLEM code block in the code below, then the y-reading in the bottom right corner will be visible, like this:
The code I used to pack the widgets:
from os.path import abspath, dirname, join
import tkinter as tk
import numpy as np
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk as NavigationToolbar
from scipy.io import wavfile
root = tk.Tk()
mainframe = tk.Frame(root)
mainframe.pack()
frame = tk.Frame(mainframe)
frame.pack()
figFrame = tk.Frame(frame)
toolFrame = tk.Frame(frame)
figFrame.pack(side='top', fill='both', expand=True)
toolFrame.pack(side='top', fill='both', expand=True)
# Place the figure
fig = plt.Figure()
figWidget = FigureCanvasTkAgg(fig, master=figFrame)
track = figWidget.get_tk_widget()
track.pack(side='top', fill='both', expand=True)
# Place the toolbar
toolbar = NavigationToolbar(figWidget, toolFrame)
toolbar.pack(side='top', fill='both', expand=True)
# Get data
SR, signal = wavfile.read(join(abspath(dirname(__file__)), 'y.wav'))
# Plot the signal read from wav file
ax = fig.add_subplot(111)
ax.set_title('Waveform and Spectrogram of a wav file')
ax.plot(signal)
ax.set_xlabel('Sample')
ax.set_ylabel('Amplitude')
# PROBLEM: Truncated y-readings in Toolbar
ax.set_ylabel('Amplitude (dB)')
ticks = ax.get_yticks()
t1 = 20*np.log10(-ticks[(ticks < 0)])
t2 = 20*np.log10(ticks[(ticks > 0)])
t1 = [float('{:.1f}'.format(i)) for i in t1]
t2 = [float('{:.1f}'.format(i)) for i in t2]
ticks = np.concatenate((t1, [-np.inf], t2))
ax.set_yticklabels(ticks)
# PROBLEM: END
plt.show()
root.mainloop()
I wonder where I did wrong. My guess is that when the ticks are hacked (my way), then there will be no reading at all.... If so, then that's a pity, because I only modified the ticks not the data.

It's clear that no useful y coordinate can be shown when you set the ticklabels manually; maybe that becomes clearer if you consider that you could have labelled the plot with "Apple", "Banana", "Cherry" - in that case what would the coordinate be when the mouse is halfway between "Banana" and "Cherry"?
You may however use a FuncFormatter to set the format of the tickslabels.
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
import numpy as np
signal = np.sin(np.linspace(0,12,300))*.7
fig, ax = plt.subplots()
ax.set_title('Waveform and Spectrogram of a wav file')
ax.plot(signal)
ax.set_xlabel('Sample')
ax.set_ylabel('Amplitude (dB)')
def fmt(x,pos=None):
if x==0:
return "-inf"
else:
return '{:.1f}'.format(20*np.log10(np.sign(x)*x))
ax.yaxis.set_major_formatter(FuncFormatter(fmt))
plt.show()

Related

Plotly backend with pandas

Hi I would like to do multiple graph (2 indeed) with pandas / backend Plotly.
I don't know how to proceed.
and what are the main option to change the size of my graph (it seems that figsize does not work) ? the color ?
I did something like that:
import pandas as pd
import matplotlib.pyplot as plt
pd.options.plotting.backend = "plotly"
f1 = data.plot(y=['vl','bench'], title='Fonds vs Bench')
f2 = data.plot(y='aum', title='AuM du fonds')
f1.show(figsize=(8,5))
f2.show(figsize=(8,5))
and would like something equivalent of (without Plotly backend):
f, (ax1,ax2) = plt.subplots(2, 1, figsize=(8,5), sharex=True)
data.plot(y=['vl', 'bench'], title='Fonds vs Bench', ax=ax1)
data.plot(y='aum', title='AuM du fonds',ax=ax2);
you can use the subplot method to create multiple subplots in a single figure. Also, you can use the update_layout method to change the size of the figure and the update_traces method to change the color of the traces.
Example:
import pandas as pd
import plotly.express as px
# set Plotly as the backend for pandas
pd.options.plotting.backend = "plotly"
# create a figure with two subplots
fig = px.subplot(rows=2, cols=1)
# add the first plot to the first subplot
fig.add_trace(px.line(data, x=data.index, y='vl', name='vl'))
fig.add_trace(px.line(data, x=data.index, y='bench', name='bench'))
fig.update_layout(title_text='Fonds vs Bench')
# add the second plot to the second subplot
fig.add_trace(px.line(data, x=data.index, y='aum', name='aum'))
fig.update_layout(title_text='AuM du fonds', row=2, col=1)
# change the size of the figure
fig.update_layout(width=800, height=500)
# change the color of one of the traces
fig.update_traces(line=dict(color='red'), selector=dict(name='vl'))
# show the figure
fig.show()
Let me know if that works for you.
Reference: https://plotly.com/python/
When creating a graph from Pandas using plotly, if you want to customize the content of the graph, you can use plotly to modify it. To modify the graph size, specify the width and height in pixels. If the desired size is 8 inches wide by 5 inches high, matplotlib's default dpi is 100 pixels, so we specified 800 pixels and 500 pixels.
import pandas as pd
import numpy as np
pd.options.plotting.backend = "plotly"
np.random.seed(20220212)
data = pd.DataFrame({'vl': np.random.randint(0,25,5),
'bench': np.random.randint(25,50,5),
'aum': np.random.randint(50,75,5)})
f1 = data.plot(y=['vl','bench'], title='Fonds vs Bench')
f2 = data.plot(y=['aum'], title='AuM du fonds')
f1.update_layout(autosize=False, width=800, height=500)
f2.update_layout(autosize=False, width=800, height=500)
f1.show()
f2.show()
Use matplotlib and seaborn:
import matplotlib.pyplot as plt
import seaborn as sns
fig, ax = plt.subplots(figsize=(20, 15))
sns.boxplot(x = 'bedrooms', y = 'price', data = dataset_df)
if you want two and more plot use
fig, ax = plt.subplots(2,2, figsize=(20, 15))
And use ax=ax[0,1]

Continious update of matplotlib plot in Jupyter

I am working on a Jupyter Notebook and I am using the following ipywidget to set a threshold value:
Thr = widgets.IntSlider(value=-17, min=-30, max=-13, step=1, description='Threshold: ', disabled=False, continuous_update=True, orientation='horizontal', readout=True, readout_format='d')
Thr
Next, I am masking a numpy array using that value with:
import numpy.ma as ma
test= ma.masked_less_equal(S_images[0], Thr.value)
And finally I plot the result with:
plt.figure(figsize = (15,15))
plt.imshow(test[0], cmap='gray')
The ipywidget is in a different Jupyter cell than the other code so when I change the value of Thr I have to manually run again the cell where the masking and ploting takes place.
My question is: I have always seen those interactive plots where you change a parameter (in my case the ipywidget Thr) and automatically the plot gets updated.
I see that widgets.IntSlider has a continuous_update parameter which seems to be close to what I want but still cannot get the behaviour I want.
Any idea if this is doable or possible?
_ EDIT _
Starting from the comment of ac24, I am adapting the example he proposes:
from IPython.display import display, clear_output
import ipywidgets as ipy
import matplotlib.pyplot as plt
import numpy as np
# setup figure
n = 10
out = ipy.Output()
# show random mesh
def update(idx):
with out:
clear_output()
fig, ax = plt.subplots(figsize = (5,5))
h = ax.imshow(S_images[0]) # here I put my image
h.set_data(np.ma.masked_less_equal(S_images[0], slider.value)) # here I set the task to masked accordint to the `slider.value`
fig.canvas.flush_events()
fig.canvas.draw()
plt.show()
slider = ipy.IntSlider(min = 0, max = 10, orientation = 'vertical')
widget = ipy.interactive(update, idx = slider)
layout = ipy.Layout(
# display = 'flex',
# flex_flow = 'row',
# justify_content = 'space-between',
# align_items = 'center',
)
widgets = ipy.HBox(children=(slider, out), layout = layout)
display(widgets)
The example works very nice and is just what I was looking for. However, I have a small question regargind the layout. Originally I am working with 3 Images so I would like to have them displayed as follows, each one with its slider next to it to do the task: (the image below is not real, just made it up to represent what I would like)
EDIT 2
in this occasion, the question is, once I select a value in the slider, I would write to geotiff that raster. For this I am using the following code:
with rasterio.open('/Path/20190331_VV_Crop') as src:
ras_meta = src.profile
with rasterio.open('/path/Threshold.tif', 'w', **ras_meta) as dst:
dst.write(X)
However, I am not sure how to reference the numpy array in dst.write(X)
I've adapted the example I gave into a class, as you want to link a specific output and slider instance, but create multiple groups of them. Setting the layout of the output widget avoids the widget resizing all the time as you slide the slider.
from IPython.display import display, clear_output
import ipywidgets as ipy
import matplotlib.pyplot as plt
import numpy as np
# setup figure
n = 10
class SliderAndImage():
# show random mesh
def update(self, idx):
with self.out:
clear_output()
fig, ax = plt.subplots(figsize = (5,5))
h = ax.imshow(np.random.rand(n, n))
h.set_data(np.random.rand(n, n))
fig.canvas.flush_events()
fig.canvas.draw()
plt.show()
def make_slider_and_image(self):
self.out = ipy.Output(layout=ipy.Layout(width='200px', height='200px'))
slider = ipy.IntSlider(min = 0, max = 10, orientation = 'vertical')
widget = ipy.interactive(self.update, idx = slider)
layout = ipy.Layout(
# display = 'flex',
# flex_flow = 'row',
# justify_content = 'space-between',
# align_items = 'center',
)
widgets = ipy.HBox(children=(slider, self.out), layout = layout)
return widgets
children = []
for _ in range(3):
widgets = SliderAndImage()
children.append(widgets.make_slider_and_image())
display(ipy.HBox(children))

Embedding seaborn clustermap into tkinter window

I'm attempting to display a seaborn clustermap in a tkinter window.
I can embed seaborn heatmaps; however, clustermaps fail to appear in the tkinter window and an empty seaborn frame appears instead.
#Libraries
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#Returns plot for tkinter to display
def create_plot():
#creates dataset
data = np.random.rand(50,11)
#Defines plot
plot, ax = plt.subplots(figsize=(11, 9))
#Does work - Outputs heatmap
#sns.heatmap(data, cmap='YlGnBu')
#Doesn't work - Should output clustermap
sns.clustermap(data,cmap='YlGnBu',metric = 'correlation',z_score=0)
return plot
#Generating tkinter window
root = tkinter.Tk()
figure = create_plot()
canvas = FigureCanvasTkAgg(figure, master=root)
canvas.draw()
canvas.get_tk_widget().pack()
tkinter.mainloop()
The clustermap can be correctly displayed inline with the following code:
%matplotlib inline
data = np.random.rand(50,11)
sns.clustermap(data,cmap='YlGnBu',metric = 'correlation',z_score=0)
Is there something different about clustermaps, how can they be embedded?
clustermap creates its own figure. So you need to return this figure to use it elsewhere.
#Libraries
import numpy as np
import seaborn as sns
import tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#Returns plot for tkinter to display
def create_plot():
data = np.random.rand(50,11)
g = sns.clustermap(data,cmap='YlGnBu',metric = 'correlation',z_score=0)
return g.fig
#Generating tkinter window
root = tkinter.Tk()
figure = create_plot()
canvas = FigureCanvasTkAgg(figure, master=root)
canvas.draw()
canvas.get_tk_widget().pack()
tkinter.mainloop()
But be aware that seaborn creates a pyplot figure. Embedding a pyplot figure in a custom GUI may cause problems (note how none of the examples from the matplotlib page on embedding use pyplot!).

Embedding matplotlib into tkinter canvas opens two windows

The following code I am working on in not behaving the way I wish it to. I have embedded a matplotlib graph into a tkinter canvas. The program opens up two windows, one of which functions properly, and one of which is not necessary.I am not sure how to fix this. Here is the code, please ignore the unnecessary imports :)
import numpy as np
import sys
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib as mpl
from matplotlib import cm
from numpy.random import random
from matplotlib.widgets import Button
import matplotlib.colors
import tkinter as tk
import matplotlib.backends.tkagg as tkagg
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
DEBUG_MODE = False #Debug mode - True = ON
MATRIX_WIDTH = 50
MATRIX_HEIGHT = 50
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
LED_COUNT = MATRIX_WIDTH * MATRIX_HEIGHT
REFRESH_RATE = 30 #REFRESH_RATE used to control FuncAnimation interval
MATRIX = random((50,50)) #Fills MATRIX as not to be null for first print
plt.rcParams['toolbar'] = 'None' #Disables matplotlib toolbar
fig = plt.figure(figsize=(3,3)) #'figsize' measured in inches
im = plt.imshow(MATRIX, interpolation='nearest', cmap=cm.Spectral)
plt.axis('off') #Turns off x, y axis
def data_gen(): #Generates amd populates MATRIX with pattern data
while True:
MATRIX = random((MATRIX_WIDTH, MATRIX_HEIGHT))
yield MATRIX
if (DEBUG_MODE): print("MATRIX yeilded")
def update(data): #Updates/prints new MATRIX from data_gen()
im.set_array(data)
if (DEBUG_MODE): print("Updated data")
root = tk.Tk()
label = tk.Label(root,text="Matrix Program").grid(column=0, row=0)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().grid(column=0,row=1)
ani = animation.FuncAnimation(fig, update, data_gen, interval=REFRESH_RATE)
plt.show()
What needs to be done to this code so that it opens only one canvas from tkinter with the live matplotlib graph embedded?
How can I set the size of the canvas?
Do not call plt.show() if you want to show your figure inside a tk GUI. Best do not use pyplot at all when embedding.
On the other hand, you probably want to start the mainloop, tk.mainloop(), at some point.
Refer to the matplotlib example on how to embedd a matplotlib figure into tk.

Python: Embed pandas plot in Tkinter GUI

I'm writing an application using pandas DataFrames in Python 2.7. I need to plot columns of my DataFrames to a Tkinter window. I know that I can plot pandas DataFrames columns using the built-in plot method on the DataFrame or Series (that is just a wrapper of the matplotlib plot function), like so:
import pandas as pd
df = pd.DataFrame({'one':[2,4,6,8], 'two':[3,5,7,9]})
df.plot('one')
Also, I figured out how to plot to a Tkinter GUI window using matplotlib:
import matplotlib
matplotlib.use('TkAgg')
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import pandas as pd
import Tkinter as tk
import ttk
root = tk.Tk()
#-------------------------------------------------------------------------------
lf = ttk.Labelframe(root, text='Plot Area')
lf.grid(row=0, column=0, sticky='nwes', padx=3, pady=3)
f = Figure(figsize=(5,4), dpi=100)
a = f.add_subplot(111)
t = arange(0.0,3.0,0.01)
s = sin(2*pi*t)
a.plot(t,s)
dataPlot = FigureCanvasTkAgg(f, master=lf)
dataPlot.show()
dataPlot.get_tk_widget().grid(row=0, column=0)
#-------------------------------------------------------------------------------
root.mainloop()
This all works as expected. What I want to do is have the pandas.DataFrame.plot() output on a Tkinter window, e.g. in the Labelframe as above. I can't get this to work. If possible, I do not want to use matplotlibs plot tools, as the pandas plot tools suit my needs much better. Is there a way to combine pandas plot() with Tkinter? Basically instead of this line:
dataPlot = FigureCanvasTkAgg(f, master=lf)
dataPlot.show()
I need this:
dataPlot = FigureCanvasTkAgg(df.plot('one'), master=lf)
dataPlot.show()
pandas uses matplotlib for plotting. Most pandas plotting functionality takes an ax kwarg that specifies the axes object that will be used. There are a few pandas functions that can't be used this way, and will always create their own figure/axes using pyplot. (e.g. scatter_matrix)
For a simple case based on your example, however:
import matplotlib
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import pandas as pd
import Tkinter as tk
import ttk
root = tk.Tk()
lf = ttk.Labelframe(root, text='Plot Area')
lf.grid(row=0, column=0, sticky='nwes', padx=3, pady=3)
t = np.arange(0.0,3.0,0.01)
df = pd.DataFrame({'t':t, 's':np.sin(2*np.pi*t)})
fig = Figure(figsize=(5,4), dpi=100)
ax = fig.add_subplot(111)
df.plot(x='t', y='s', ax=ax)
canvas = FigureCanvasTkAgg(fig, master=lf)
canvas.show()
canvas.get_tk_widget().grid(row=0, column=0)
root.mainloop()

Categories

Resources