In matplotlib I wish to know the cleanest and most robust means of overlaying labels onto an axis. This is probably best demonstrated with an example:
While normal axis labels/ticks are placed every 5.00 units additional labels without ticks have been overlayed onto the axis (this can be seen at 1113.75 which partially covers 1114.00 and 1105.00 which is covered entirely). The labels also have the same font and size as their normal, ticked, counterparts with the background (if any) going right up to the axis (as a tick mark would).
What is the simplest way of obtaining this effect in matplotlib?
Edit
Following on from #Ken's suggestion I have managed to obtain the effect for an existing tick/label by using ax.yaxis.get_ticklines and ax.yaxis.get_ticklabels to both remove the tick marker and change the background/font/zorder of a label. However, I am unsure how best to add a new tick/label to an axis.
In other words I am looking for a function add_tick(ax.yaxis, loc) that adds a tick at location loc and returns the tickline and ticklabel objects for me to operate on.
I haven't ever tried to do that, but I think that the Artist tutorial might be helpful for you. In particular, the last section has the following code:
for line in ax1.yaxis.get_ticklines():
# line is a Line2D instance
line.set_color('green')
line.set_markersize(25)
line.set_markeredgewidth(3)
I think that using something like line.set_markersize(0) might make the markers have size zero. The difficult part might be finding the ones that need that done. It is possible that the line.xdata or line.ydata arrays might contain enough information to isolate the ones you need. Of course, if you are manually adding the tick marks, it is possible that as you do that the instance gets returned, so you can just modify them as you create them.
The best solution I have been able to devise:
# main: axis; olocs: locations list; ocols: location colours
def overlay_labels(main, olocs, ocols):
# Append the overlay labels as ticks
main.yaxis.set_ticks(np.append(main.yaxis.get_ticklocs(), olocs))
# Perform generic formatting to /all/ ticks
# [...]
labels = reversed(main.yaxis.get_ticklabels())
markers = reversed(main.yaxis.get_ticklines()[1::2]) # RHS ticks only
glines = reversed(main.yaxis.get_gridlines())
rocols = reversed(ocols)
# Suitably format each overlay tick (colours and lines)
for label,marker,grid,colour in izip(labels, markers, glines, rocols):
label.set_color('white')
label.set_backgroundcolor(colour)
marker.set_visible(False)
grid.set_visible(False)
It is not particularly elegant but does appear to work.
Related
I have been trying to make a figure using plotly that combines multiple figures together. In order to do this, I have been trying to use the make_subplots function, but I have found it very difficult to have the plots added in such a way that they are properly formatted. I can currently make singular plots (as seen directly below):
However, whenever I try to combine these singular plots using make_subplots, I end up with this:
This figure has the subplots set up completely wrong, since I need each of the four subplots to contain data pertaining to the four methods (A, B, C, and D). In other words, I would like to have four subplots that look like my singular plot example above.
I have set up the code in the following way:
for sequence in sequences:
#process for making sequence profile is done here
sequence_df = pd.DataFrame(sequence_profile)
row_number=1
grand_figure = make_subplots(rows=4, cols=1)
#there are four groups per sequence, so the grand figure should have four subplots in total
for group in sequence_df["group"].unique():
figure_df_group = sequence_df[(sequence_df["group"]==group)]
figure_df_group.sort_values("sample", ascending=True, inplace=True)
figure = px.line(figure_df_group, x = figure_df_group["sample"], y = figure_df_group["intensity"], color= figure_df_group["method"])
figure.update_xaxes(title= "sample")
figure.update_traces(mode='markers+lines')
#note: the next line fails, since data must be extracted from the figure, hence why it is commented out
#grand_figure.append_trace(figure, row = row_number, col=1)
figure.update_layout(title_text="{} Profile Plot".format(sequence))
grand_figure.append_trace(figure.data[0], row = row_number, col=1)
row_number+=1
figure.write_image(os.path.join(output_directory+"{}_profile_plot_subplots_in_{}.jpg".format(sequence, group)))
grand_figure.write_image(os.path.join(output_directory+"grand_figure_{}_profile_plot_subplots.jpg".format(sequence)))
I have tried following directions (like for example, here: ValueError: Invalid element(s) received for the 'data' property) but I was unable to get my figures added as is as subplots. At first it seemed like I needed to use the graph object (go) module in plotly (https://plotly.com/python/subplots/), but I would really like to keep the formatting/design of my current singular plot. I just want the plots to be conglomerated in groups of four. However, when I try to add the subplots like I currently do, I need to use the data property of the figure, which causes the design of my scatter plot to be completely messed up. Any help for how I can ameliorate this problem would be great.
Ok, so I found a solution here. Rather than using the make_subplots function, I just instead exported all the figures onto an .html file (Plotly saving multiple plots into a single html) and then converted it into an image (HTML to IMAGE using Python). This isn't exactly the approach I would have preferred to have, but it does work.
UPDATE
I have found that plotly express offers another solution, as the px.line object has the parameter of facet that allows one to set up multiple subplots within their plot. My code is set up like this, and is different from the code above in that the dataframe does not need to be iterated in a for loop based on its groups:
sequence_df = pd.DataFrame(sequence_profile)
figure = px.line(sequence_df, x = sequence_df["sample"], y = sequence_df["intensity"], color= sequence_df["method"], facet_col= sequence_df["group"])
Although it still needs more formatting, my plot now looks like this, which is works much better for my purposes:
Matplotlib allows changing the alpha value of almost anything, but how does it work for an ticklabel?
If I have a text, it is easy:
ax.set_xticklabel(labels, alpha=alpha)
The case is different if I do not have a text as the following throws a TypeError, due to missing labels.
ax.set_xticklabel(alpha=alpha)
Therefore, my next idea was to get the automatically generated ticklabels and use them to do the job:
labels = [label.get_text() for label in ax.get_xticklabels()]
ax.set_xticklabels(labels, alpha=alpha
The problem here is, the labels are empty due to the dynamic nature of matplotlib (see here).
So, is there an easy way to change the alpha of my ticklabels without knowing the text beforehand?
It is probably not desirable to set the ticklabels themselves, if you want to change their color. The reason is that setting the labels via ax.set_ticklabels changes the formatter to a FixedFormatter; with this one would loose the automatic formatting behaviour.
Instead change the alpha of the text objects that later constitute the ticklabels. To this end plt.setp is a useful feature.
plt.setp(ax.get_xticklabels(), alpha=0.3)
The same can be achieved via
for t in ax.get_xticklabels():
t.set_alpha(0.3)
I'm hoping to setup a method which can convert a normal figure (dark lines, white/transparent background) to a pseudo-inverted figure (light lines, black/transparent background). I could just post-process invert the image, but directly inverted colors look awful, so I'd like to instead (try to) create a mapping from one set of colors to another, and then apply this to all artists which have been added to (all axes on) a figure.
Is there a way to access all objects (e.g. text, scatter, lines, ticklabels, etc) that have been added to a figure?
Edit: my motivation is to automatically create white-background and black-background versions of figures. White-background figures will always (I think) be required for publications (for example), while black-background figures may be better for presentations (i.e. talk slides). While it wouldn't be that much trouble to setup a flag, and change each color based on that, e.g.
if dark:
col_line = 'cyan'
col_bg = 'black'
else:
col_line = 'red'
col_bg = 'white'
# ... plot ...
It would be much cooler and more convenient (despite overhead) to do something like,
fig.savefig('dark.pdf')
invert(fig)
fig.savefig('light.pdf')
Recursively call .get_children(), stop when the returned list is empty.
You can use a different style or change an existing style to your needs instead of changing all properties of all possible artists yourself.
E.g. you might start with the "dark_background" style and then adjust some further parameters using rcParams.
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("dark_background")
style = {"lines.linewidth" : 2,
"axes.facecolor" : "#410448"}
plt.rcParams.update(style)
plt.plot(np.sin(np.linspace(0, 2 * np.pi)))
plt.show()
I am manually putting a bunch of boxplots in a plot.
The code I am using is this (I am computing mean_, iqr, CL, etc. elsewhere):
A = np.random.random(2)
D = plt.boxplot(A, positions=np.atleast_1d(dist_val), widths=np.min(unique_dists_vals) / 10.) # a simple case with just one variable to boxplot
D['medians'][0].set_ydata(median_)
D['boxes'][0]._xy[[0,1,4], 1] = iqr[0]
D['boxes'][0]._xy[[2,3],1] = iqr[1]
D['whiskers'][0].set_ydata(np.array([iqr[0], CL[0]]))
D['whiskers'][1].set_ydata(np.array([iqr[1], CL[1]]))
D['caps'][0].set_ydata(np.array([CL[0], CL[0]]))
D['caps'][1].set_ydata(np.array([CL[1], CL[1]]))
I do this in a loop, putting one box plot per some location x.
I am not making any changes to the axis limits. The resulting figure looks like this:
what is going on with 1 x-tick?
the limits are just off on both x and y.
This appears to be a bug?
And no, I cannot just manually set the limits etc. since this has to be a completely general code.
What I have tried so far is:
During the loop when I compute the box plots, try keeping track of the largest y value seen so far and the largest x value etc. and then at the end manually set the bound to this. Other issues come up here, however, such as boxes extending beyond the plot etc. and then I manually have to adjust the limits to extend beyond the box width etc.
I have used both "ax.axis('auto')" and "ax.set_autoscale_on(True)" after plotting right before plt.show(), does not work:
While the first item in the list above does technically work (not ideal) I would like to know if there is a generic way to simply say: "done plotting, fix limits" (should automatically be done while plotting I guess?).
Thank you.
I am new to pandas and matplotlib, but not to Python. I have two questions; a primary and a secondary one.
Primary:
I have a pandas boxplot with FICO score on the x-axis and interest rate on the y-axis.
My x-axis is all messed up since the FICO scores are overwriting each other.
I'd like to show only every 4th or 5th ticklabel on the x-axis for a couple of reasons:
in general it's less chart-junky
in this case it will allow the labels to actually be read.
My code snippet is as follows:
plt.figure()
loansmin = pd.read_csv('../datasets/loanf.csv')
p = loansmin.boxplot('Interest.Rate','FICO.Score')
I saved the return value in p as I thought I might need to manipulate the plot further which I do now.
Secondary:
How do I access the plot, subplot, axes objects from pandas boxplot.
p above is an matplotlib.axes.AxesSubplot object.
help(matplotlib.axes.AxesSubplot) gives a message saying:
'AttributeError: 'module' object has no attribute 'AxesSubplot'
dir(matplotlib.axes) lists Axes, Subplot and Subplotbase as in that namespace but no AxesSubplot. How do I understand this returned object better?
As I explored further I found that one could explore the returned object p via dir().
Doing this I found a long list of useful methods, amongst which was set_xticklabels.
Doing help(p.set_xticklabels) gave some cryptic, but still useful, help - essentially suggesting passing in a list of strings for ticklabels.
I then tried doing the following - adding set_xticklabels to the end of the last line in the above code effectively chaining the invocations.
plt.figure()
loansmin = pd.read_csv('../datasets/loanf.csv')
p=loansmin.boxplot('Interest.Rate','FICO.Score').set_xticklabels(['650','','','','','700'])
This gave the desired result. I suspect there's a better way as in the way matplotlib does it which allows you to show every n'th label. But for immediate use this works, and also allows setting labels where they are not periodic for whatever reason, if you need that.
As usual, writing out the question explicitly helped me find the answer. And if anyone can help me get to the underlying matplotlib object that is still an open question.
AxesSubplot (I think) is just another way to get at the Axes in matplotlib. set_xticklabels() is part of the matplotlib object oriented interface (on axes). So, if you were using something like pylab, you might use xticks(ticks, labels), but instead here you have to separate it into different calls ax.set_xticks(ticks), ax.set_xticklabels(labels). (where ax is an Axes object).
Let's say you only want to set ticks at 650 and 700. You could do the following:
ticks = labels = [650, 700]
plt.figure()
loansmin = pd.read_csv('../datasets/loanf.csv')
p=loansmin.boxplot('Interest.Rate','FICO.Score')
p.set_xticks(ticks)
p.set_xticklabels(labels)
Similarly, you can use set_xlim and set_ylim to do the equivalent of xlim() and ylim() in plt.