Trying to use seaborn and matplotlib to plot some data, need to add some descriptive text to my plot, normally I'd just use the matplotlib command text, and place it where I wanted relative to the axes, but it doesn't appear to work at all, I get no text showing beyond the default stuff on the axes, ticks, etc. What I want is some custom text showing in the top left corner of the plot area.
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
df is my pandas dataframe, it just contains some columns of time and coordinate data with a tag "p" which is an identifier.
ax2 = sns.scatterplot("t","x", data = df, hue = "p")
ax2.text(0.1, 0.9, r"$s = {}, F = {}, N = {}$".format(value1, valu2, value3))
plt.show()
Anyone know how I can get some text to show, relatively positioned, the "value" items are just the variables with the data I want to print. Thanks.
You want to position a text "in the top left corner of the plot area". The "plot area" is called axes. Three solutions come to mind:
Text in axes coordinates
You could specify the text in axes coordinates. Those range from (0,0) in the lower left corner of the axes to (1,1) in the top right corner of the axes. The corresponding transformation is obtained via ax.transAxes.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.text(0.02, 0.98, "Text", ha="left", va="top", transform=ax.transAxes)
plt.show()
Annotation with offset
In the above the distance between the text and the top left corner will be dependent on the size of the axes. It might hence be beneficial to position the text exactly at the top left corner (i.e. (0,1) in axes coordinates) and then offset it by some points, i.e in absolute units.
ax.annotate("Text", xy=(0,1), xycoords="axes fraction",
xytext=(5,-5), textcoords="offset points",
ha="left", va="top")
The result here looks similar to the above, but is independent of the axes or figure size; the text will always be 5 pts away from the top left corner.
Text at an anchored position
Finally, you may not actually want to specify any coordinates at all. After all "upper left" should be enough as positioning information. This would be achieved via an AnchoredText as follows.
import matplotlib.pyplot as plt
from matplotlib.offsetbox import AnchoredText
fig, ax = plt.subplots()
anc = AnchoredText("Text", loc="upper left", frameon=False)
ax.add_artist(anc)
plt.show()
In order to position the text in the upper left corner for a plot without knowing the limits beforehand you can query the x and y limits of the axis and use that to position the text relative to the bounds of the plot. Consider this example (where I have also included code to generate some random data for demonstration)
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
np.random.seed(1)
df = pd.DataFrame({'t':np.linspace(1,2,100),
'x':np.random.randn(100)})
value1 = 1
value2 = 2
value3 = 3
ax2 = sns.scatterplot("t","x", data = df)
tl = ((ax2.get_xlim()[1] - ax2.get_xlim()[0])*0.010 + ax2.get_xlim()[0],
(ax2.get_ylim()[1] - ax2.get_ylim()[0])*0.95 + ax2.get_ylim()[0])
ax2.text(tl[0], tl[1], r"$s = {}, F = {}, N = {}$".format(value1, value2, value3))
plt.show()
This will output
and changing the bounds will not change the position of the text, i.e.
You may need to adjust the multipliers 0.01 and 0.95 as you want based on exactly how close to the corner you want the text.
Related
For an assignment, I am trying to recreate the rose plot
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
nmax=101 # choose a high number to "smooth out" lines in plots
x_angle = np.linspace(0,2*np.pi,nmax) # create an array x for bottom right
y_br = abs(np.cos(3*x_angle)) # y for the bottom right subplot
# bottom right subplot controls
plt.plot(x_angle, y_br, 'tab:blue')
Is there a way to get it to look any closer to the original plot (e.g. make the petals narrower, set tick marks to 0.0, 0.5, 1.0)?
You can use polar projection for this type of plot:
for the ticks, it is set by rticks
for the petals, it is controlled by the nmax feature
Here is a code that gives a better drawing:
import numpy as np
import matplotlib.pyplot as plt
nmax=int(1e5) # choose a high number to "smooth out" lines in plots
x_angle = np.linspace(0,2*np.pi,nmax) # create an array x for bottom right
y_br = abs(np.cos(3*x_angle)) # y for the bottom right subplot
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(x_angle, y_br, lw= 3)
ax.set_rticks([0, 0.5, 1]) # Less radial ticks
I am trying to make a series of matplotlib plots that plot timespans for different classes of objects. Each plot has an identical x-axis and plot elements like a title and a legend. However, which classes appear in each plot differs; each plot represents a different sampling unit, each of which only contains only a subset of all the possible classes.
I am having a lot of trouble determining how to set the figure and axis dimensions. The horizontal size should always remain the same, but the vertical dimensions need to be scaled to the number of classes represented in that sampling unit. The distance between each entry on the y-axis should be equal for every plot.
It seems that my difficulties lie in the fact that I can set the absolute size (in inches) of the figure with plt.figure(figsize=(w,h)), but I can only set the size of the axis with relative dimensions (e.g., fig.add_axes([0.3,0.05,0.6,0.85]) which leads to my x-axis labels getting cut off when the number of classes is small.
Here is an MSPaint version of what I'd like to get vs. what I'm getting.
Here is a simplified version of the code I have used. Hopefully it is enough to identify the problem/solution.
import pandas as pd
import matplotlib.pyplot as plt
import pylab as pl
from matplotlib import collections as mc
from matplotlib.lines import Line2D
import seaborn as sns
# elements for x-axis
start = 1
end = 6
interval = 1 # x-axis tick interval
xticks = [x for x in range(start, end, interval)] # create x ticks
# items needed for legend construction
lw_bins = [0,10,25,50,75,90,100] # bins for line width
lw_labels = [3,6,9,12,15,18] # line widths
def make_proxy(zvalue, scalar_mappable, **kwargs):
color = 'black'
return Line2D([0, 1], [0, 1], color=color, solid_capstyle='butt', **kwargs)
for line_subset in data:
# create line collection for this run through loop
lc = mc.LineCollection(line_subset)
# create plot and set properties
sns.set(style="ticks")
sns.set_context("notebook")
############################################################
# I think the problem lies here
fig = plt.figure(figsize=(11, len(line_subset.index)*0.25))
ax = fig.add_axes([0.3,0.05,0.6,0.85])
############################################################
ax.add_collection(lc)
ax.set_xlim(left=start, right=end)
ax.set_xticks(xticks)
ax.xaxis.set_ticks_position('bottom')
ax.margins(0.05)
sns.despine(left=True)
ax.set_yticks(line_subset['order_y'])
ax.set(yticklabels=line_subset['ylabel'])
ax.tick_params(axis='y', length=0)
# legend
proxies = [make_proxy(item, lc, linewidth=item) for item in lw_labels]
leg = ax.legend(proxies, ['0-10%', '10-25%', '25-50%', '50-75%', '75-90%', '90-100%'], bbox_to_anchor=(1.0, 0.9),
loc='best', ncol=1, labelspacing=3.0, handlelength=4.0, handletextpad=0.5, markerfirst=True,
columnspacing=1.0)
for txt in leg.get_texts():
txt.set_ha("center") # horizontal alignment of text item
txt.set_x(-23) # x-position
txt.set_y(15) # y-position
You can start by defining the margins on top and bottom in units of inches. Having a fixed unit of one data unit in inches allows to calculate how large the final figure should be.
Then dividing the margin in inches by the figure height gives the relative margin in units of figure size, this can be supplied to the figure using subplots_adjust, given the subplots has been added with add_subplot.
A minimal example:
import numpy as np
import matplotlib.pyplot as plt
data = [np.random.rand(i,2) for i in [2,5,8,4,3]]
height_unit = 0.25 #inch
t = 0.15; b = 0.4 #inch
for d in data:
height = height_unit*(len(d)+1)+t+b
fig = plt.figure(figsize=(5, height))
ax = fig.add_subplot(111)
ax.set_ylim(-1, len(d))
fig.subplots_adjust(bottom=b/height, top=1-t/height, left=0.2, right=0.9)
ax.barh(range(len(d)),d[:,1], left=d[:,0], ec="k")
ax.set_yticks(range(len(d)))
plt.show()
I'd like to find a way to make an annotation that automatically aligns with the label text of a colorbar. Take this example:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(5,10))
data = np.arange(1000, 0, -10).reshape(10, 10)
im = ax.imshow(data, cmap='Blues')
clb = plt.colorbar(im, shrink=0.4)
clb.ax.annotate('text', xy=(1, -0.075), xycoords='axes fraction')
I want to have the last t of "text" to be on the same x coordinate as the last 0 of 1000 in the colorbar label. I can do so manually by adjusting the xy parameter in annotate, but I have to do this for many graphs and would like to find a way to get the parameter from somewhere automatically.
How can I get the maximum x coordinate of the text labes and annotate in a way where the annotation ends on that coordinate? Could someone point me in the right direction? Thanks a lot!
Since the labels are left-aligned, but you want to align your additional text according to the end of that label, I fear there is no other choice than to find out the coordinates from the drawn figure and place the label accordingly.
import matplotlib.pyplot as plt
from matplotlib import transforms
import numpy as np
fig, ax = plt.subplots(figsize=(5,4))
data = np.arange(1000, 0, -10).reshape(10, 10)
im = ax.imshow(data, cmap='Blues')
cbar = plt.colorbar(im)
# draw figure first to be able to retrieve coordinates
fig.canvas.draw()
# get the bounding box of the last label
bbox = cbar.ax.get_yticklabels()[-1].get_window_extent()
# calculate pixels back to axes coords
labx,_ = cbar.ax.transAxes.inverted().transform([bbox.x1,0])
ax.annotate('text', xy=(labx, -0.075), xycoords=cbar.ax.transAxes,
ha = "right")
plt.show()
Note that this approach will fail once you change the figure size afterwards or change the layout in any other way. It should hence always come last in your code.
I am trying to center an axis text object by:
Getting the width in coordinates of the text divided by 2.
Subtracting that value from the center (provided) location on the x-axis.
Using the resulting value as the x starting position (with ha='left').
I have seen examples of how to get x-coordinates (bounds) after plotting a string like this:
import matplotlib as plt
f = plt.figure()
r = f.canvas.get_renderer()
t = plt.text(0, 0, 'test')
bb = t.get_window_extent(renderer=r)
width = bb.width
However, I would like to know the width (in axis coordinates) of a string before plotting so that I can anticipate an adjustment to make.
I've tried the following, but it did not return the correct axis coordinates and I think a transformation may need to occur:
t = matplotlib.textpath.TextPath((0,0), 'test', size=9)
bb = t.get_extents()
w = bb.width #16.826132812499999
Here's a sample to work with (last 3 lines show what I want to do):
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
%matplotlib inline
prf=[60,70,65,83,77,70,71]
figsize=3.5,4
fig, ax = plt.subplots(1, 1, figsize = figsize, dpi=300)
ind=np.arange(len(prf))
p=ax.bar(ind,prf,color=colrs,edgecolor='none')
t = matplotlib.textpath.TextPath((0,0), 'test', size=9)
bb = t.get_extents()
w = bb.width
center=len(ind)/2
xposition=center-w/2
ax.text(xposition,110,'test',size=9)
This question is a follow-up from this post.
I know I can use ha='center', but this is actually for a more complex text (multi-colored), which does not provide that option.
Thanks in advance!
You can create a text object, obtain its bounding box and then remove the text again. You may transform the bounding box into data coordinates (I assume that you mean data coordinates, not axes coordinates in the question) and use those to create a left aligned text.
import matplotlib.pyplot as plt
ax = plt.gca()
ax.set_xlim(0,900)
#create centered text
text = ax.text(400,0.5, "string", ha="center", color="blue")
plt.gcf().canvas.draw()
bb = text.get_window_extent()
# remove centered text
text.remove()
del text
# create left aligned text from position of centered text
bb2 = bb.transformed(ax.transData.inverted())
text = ax.text(bb2.x0,0.5, "string", ha="left", color="red")
plt.show()
Is it possible to fill with a color the area outside the two threshold lines (line1 and line2) and limited in Y-axis by the KDE curve drawn by distplot ?(that represents 3-sigmas for my application)
import pylab as pl
import seaborn as sns
#plotting the two lines
p1 = pl.axvline(x=line1,color='#EF9A9A')
p2 = pl.axvline(x=line2,color='#EF9A9A')
#plotting the PDF
sns.distplot(stat, hist=True,color='#388E3C')
You may use fill_between to fill the area underneath a curve. To get access to the KDE curve from the seaborn plot, you can draw that one first, such that ax.lines only has a single element, which is the curve of interest. Its data is obtained via kde_x, kde_y = ax.lines[0].get_data().
Then using ax.fill_between() allows to fill the area under the curve. To restrict this to be outside some given data range, the where keyword argument may be used (and interpolate=True should be set to have the area go up to the points in question).
ax.fill_between(kde_x, kde_y, where=(kde_x<x0) | (kde_x>x1) ,
interpolate=True, color='#EF9A9A')
Full example:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
stat=np.random.randn(100)
x0 = -1
x1 = 1
#plotting the PDF (do this before plotting anything else)
ax = sns.distplot(stat, hist=True,color='#388E3C')
kde_x, kde_y = ax.lines[0].get_data()
#plotting the two lines
p1 = plt.axvline(x=x0,color='#EF9A9A')
p2 = plt.axvline(x=x1,color='#EF9A9A')
ax.fill_between(kde_x, kde_y, where=(kde_x<x0) | (kde_x>x1) ,
interpolate=True, color='#EF9A9A')
plt.show()
Old answer to initial question:
You may use an axvspan, starting at the left x limit and going to the position of the first line and another one starting at the position of the second line and going to the right x limit.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
stat=np.random.randn(100)
x0 = -1
x1 = 1
#plotting the two lines
p1 = plt.axvline(x=x0,color='#EF9A9A')
p2 = plt.axvline(x=x1,color='#EF9A9A')
#plotting the PDF
ax = sns.distplot(stat, hist=True,color='#388E3C')
xlim = ax.get_xlim()
ax.axvspan(xlim[0], x0, color='#EF9A9A', alpha=0.5)
ax.axvspan(x1, xlim[1], color='#EF9A9A', alpha=0.5)
#reset xlim
ax.set_xlim(xlim)
plt.show()
Here, we need to adjust the xlimits after setting spans; the reason is that with the spans in place the autoscaling would add another 5% padding to both ends of the axes, resulting in white space. Alternatively you could use zero margin for the xaxis, ax.margins(x=0).