Python 3 matplotlib add a watermark with multiple scale axis - python-3.x

In python 3, i am trying to add watermark with multiple scale axis in the following pandas data frame
index,as_of_date,Total_10bn,close
0,2020-08-05,620.55975367473,332.11
1,2020-08-12,621.9414641848599,337.44
2,2020-08-19,628.88298116372,337.23
3,2020-08-26,627.26943375402,347.57
4,2020-09-02,630.01703674403,357.7
5,2020-09-09,630.70673674269,339.79
6,2020-09-16,637.50390815142,338.82
I can make the multiple scale works
df_soma_spy=pd.read_csv('df_soma_spy.csv')
print(df_soma_spy)
# create figure and axis objects with subplots()
fig,ax = plt.subplots()
plt.xticks(rotation=90)
ax.plot(df_soma_spy.as_of_date, df_soma_spy.Total_10bn, color="red") ## , marker="o"
# set x-axis label
ax.set_xlabel("Date", fontsize=12)
# set y-axis label
ax.set_ylabel("Fed SOMA ($10bn)",color="red",fontsize=14)
plt.grid(True, axis='both', which='both')
# twin object for two different y-axis on the sample plot
ax2=ax.twinx()
# make a plot with different y-axis using second axis object
ax2.plot(df_soma_spy.as_of_date, df_soma_spy["close"], color="black") ## , marker="o"
ax2.set_ylabel("$SPY Price",color="black",fontsize=14)
plt.title('Federal Reserves SOMA Total vs $SPY')
plt.show()
# save the plot as a file
fig.savefig('soma_spy.png',
format='jpeg',
dpi=300,
bbox_inches='tight')
Now I am trying to add a logo behind the picture. But no matter how I try, it will mess up one of the axis.
For example
import matplotlib.image as image
im = image.imread('xxx.png')
myaximage = ax.imshow(im, aspect='auto', extent=(0.1,0.1,0.1,0.1), alpha=0.5, zorder=-1)
In this case, the logo doesn't show up and the red axis is totally messed up.
There are some other solutions but none of them seems to work.
Scale image in matplotlib without changing the axis
Matplotlib automate placement of watermark
Scale image in matplotlib without changing the axis
Any thoughts? Thank you!

Instead of ax.imshow(), you can use fig.figimage() as shown below and described here. Just insert the following two lines in your code:
logo = image.imread(fname='logo.png')
fig.figimage(logo,alpha= 0.1)
Using the partial data you provided, here is the saved image:

Related

How to align heights and widths subplot axes with gridspec and matplotlib?

I am trying to use matplotlib with gridspec to create a subplot such that the axes are arranged to look similar to the figure below; the figure was taken from this unrelated question.
My attempt at recreating this axes arrangement is below. Specifically, my problem is that the axes are not properly aligned. For example, the axis object for the blue histogram is taller than the axis object for the image with various shades of green; the orange histogram seems to properly align in terms of width, but I attribute this to luck. How can I properly align these axes? Unlike the original figure, I would like to add/pad extra empty space between axes such that there borders do not intersect; the slice notation in the code below does this by adding a blank row/column. (In the interest of not making this post longer than it has to be, I did not make the figures "pretty" by playing with axis ticks and the like.)
Unlike the original picture, the axes are not perfectly aligned. Is there a way to do this without using constrained layout? By this, I mean some derivative of fig, ax = plt.subplots(constrained_layout=True)?
The MWE code to recreate my figure is below; note that there was no difference between ax.imshow(...) and ax.matshow(...).
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize
## initialize figure and axes
fig = plt.figure()
gs = fig.add_gridspec(6, 6, hspace=0.2, wspace=0.2)
ax_bottom = fig.add_subplot(gs[4:, 2:])
ax_left = fig.add_subplot(gs[:4, :2])
ax_big = fig.add_subplot(gs[:4, 2:])
## generate data
x = np.random.normal(loc=50, scale=10, size=100)
y = np.random.normal(loc=500, scale=50, size=100)
## get singular histograms
x_counts, x_edges = np.histogram(x, bins=np.arange(0, 101, 5))
y_counts, y_edges = np.histogram(y, bins=np.arange(0, 1001, 25))
x_mids = (x_edges[1:] + x_edges[:-1]) / 2
y_mids = (y_edges[1:] + y_edges[:-1]) / 2
## get meshed histogram
sample = np.array([x, y]).T
xy_counts, xy_edges = np.histogramdd(sample, bins=(x_edges, y_edges))
## subplot histogram of x
ax_bottom.bar(x_mids, x_counts,
width=np.diff(x_edges),
color='darkorange')
ax_bottom.set_xlim([x_edges[0], x_edges[-1]])
ax_bottom.set_ylim([0, np.max(x_counts)])
## subplot histogram of y
ax_left.bar(y_mids, y_counts,
width=np.diff(y_edges),
color='steelblue')
ax_left.set_xlim([y_edges[0], y_edges[-1]])
ax_left.set_ylim([0, np.max(y_counts)])
## subplot histogram of xy-mesh
ax_big.imshow(xy_counts,
cmap='Greens',
norm=Normalize(vmin=np.min(xy_counts), vmax=np.max(xy_counts)),
interpolation='nearest',
origin='upper')
plt.show()
plt.close(fig)
EDIT:
One can initialize the axes by explicitly setting width_ratios and height_ratios per row/column; this is shown below. This doesn't affect the output, but maybe I'm using it incorrectly?
## initialize figure and axes
fig = plt.figure()
gs = gridspec.GridSpec(ncols=6, nrows=6, figure=fig, width_ratios=[1]*6, height_ratios=[1]*6)
ax_bottom = fig.add_subplot(gs[4:, 2:])
ax_left = fig.add_subplot(gs[:4, :2])
ax_big = fig.add_subplot(gs[:4, 2:])
The problem is with imshow, which resizes the axes automatically to maintain a square pixel aspect.
You can prevent this by calling:
ax_big.imshow(..., aspect='auto')

Using "hue" for a Seaborn visual: how to get legend in one graph?

I created a scatter plot in seaborn using seaborn.relplot, but am having trouble putting the legend all in one graph.
When I do this simple way, everything works fine:
import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
import seaborn as sns
df2 = df[df.ln_amt_000s < 700]
sns.relplot(x='ln_amt_000s', y='hud_med_fm_inc', hue='outcome', size='outcome', legend='brief', ax=ax, data=df2)
The result is a scatter plot as desired, with the legend on the right hand side.
However, when I try to generate a matplotlib figure and axes objects ahead of time to specify the figure dimensions I run into problems:
a4_dims = (10, 10) # generating a matplotlib figure and axes objects ahead of time to specify figure dimensions
df2 = df[df.ln_amt_000s < 700]
fig, ax = plt.subplots(figsize = a4_dims)
sns.relplot(x='ln_amt_000s', y='hud_med_fm_inc', hue='outcome', size='outcome', legend='brief', ax=ax, data=df2)
The result is two graphs -- one that has the scatter plots as expected but missing the legend, and another one below it that is all blank except for the legend on the right hand side.
How do I fix this such? My desired result is one graph where I can specify the figure dimensions and have the legend at the bottom in two rows, below the x-axis (if that is too difficult, or not supported, then the default legend position to the right on the same graph would work too)? I know the problem lies with "ax=ax", and in the way I am specifying the dimensions as matplotlib figure, but I'd like to know specifically why this causes a problem so I can learn from this.
Thank you for your time.
The issue is that sns.relplot is a "Figure-level interface for drawing relational plots onto a FacetGrid" (see the API page). With a simple sns.scatterplot (the default type of plot used by sns.relplot), your code works (changed to use reproducible data):
df = pd.read_csv("https://vincentarelbundock.github.io/Rdatasets/csv/datasets/iris.csv", index_col=0)
fig, ax = plt.subplots(figsize = (5,5))
sns.scatterplot(x = 'Sepal.Length', y = 'Sepal.Width',
hue = 'Species', legend = 'brief',
ax=ax, data = df)
plt.show()
Further edits to legend
Seaborn's legends are a bit finicky. Some tweaks you may want to employ:
Remove the default seaborn title, which is actually a legend entry, by getting and slicing the handles and labels
Set a new title that is actually a title
Move the location and make use of bbox_to_anchor to move outside the plot area (note that the bbox parameters need some tweaking depending on your plot size)
Specify the number of columns
fig, ax = plt.subplots(figsize = (5,5))
sns.scatterplot(x = 'Sepal.Length', y = 'Sepal.Width',
hue = 'Species', legend = 'brief',
ax=ax, data = df)
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles=handles[1:], labels=labels[1:], loc=8,
ncol=2, bbox_to_anchor=[0.5,-.3,0,0])
plt.show()

How to change the font weight of individual colorbar labels?

I would like to have different font weights for each of my colorbar labels.
I have tried to let LaTeX format the labels in the following way:
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
im = ax.imshow(np.random.rand(50, 50)/20)
cbar = ax.figure.colorbar(im, ticks=np.arange(0, 0.05, 0.01))
cbar.ax.set_yticklabels([r'{\fontsize{50pt}{3em}\selectfont{}{0}}',
r'{\fontsize{40pt}{3em}\selectfont{}{0.01}}',
r'{\fontsize{30pt}{3em}\selectfont{}{0.03}}',
r'{\fontsize{20pt}{3em}\selectfont{}{0.03}}',
r'{\fontsize{10pt}{3em}\selectfont{}{0.04}}',
r'{\fontsize{1pt}{3em}\selectfont{}{0.05}}', ])
but this only updates the text of the labels to the whole string (e.g., {\fontsize{50pt}{3em}\selectfont{}{0}}). The pyplot TeX demo works for me. Even if this solution would work it would not be ideal as I would probably need to specify everything manually.
Much more convenient would be something like in this question. There, I learned that the font size of single labels of the regular x and y axis can be updated by calling
label = axes.yaxis.get_major_ticks()[2].label
label.set_fontsize(size)
replacing set_fontsize by set_fontweight correctly updates the weight of the selected label.
Unfortunately I could not find the equivalent of axes.yaxis.get_major_ticks()[2].label for the colorbar.
Is it possible to change the font weight of individual labels of the colorbar directly? With directly I mean without using a workaround like plotting some new text above existing labels.
If this is not possible a solution plotting text above existing labels which automatically uses the position and content the previous labels and only adjusts the font weight would also be appreciated.
Thanks!
As pointed out by #ImportanceOfBingErnest , set_fontweight works for setting the weight of single colorbar labels too.
I had to try a couple of things to find which call would give me the text objects defining the colorbar labels. They are accessible in cbar.ax.get_yticklabels().
The code snippet below now properly changes the weight of the second colorbar label:
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
im = ax.imshow(np.random.rand(50, 50)/20)
cbar = ax.figure.colorbar(im, ticks=np.arange(0, 0.05, 0.01))
cbar.ax.get_yticklabels()[1].set_fontweight(1000)
plt.show()
Output of code (not enough reputation for inline images)

Increasing plot size with multiple plots?

I am trying to plot a histogram with my data.
Using python on Jupyter notebook
viz = cdf[['GyrNative', 'GyMutant', 'Hbond_native', 'HMutant', 'RMSDNative','RMSDMutant', 'RMSFNative', 'RMSFMutant', 'SASANative', 'SASAMutant']]
plt.figure(figsize = (15,10))
viz.hist(grid=True, rwidth = 0.9, color ='red')
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=0.1)
plt.show()
The plot generated are really tiny... How may I increase the size of each plot at once?
Following from the comments, if you just want to make the whole thing bigger, you should just add figsize to this and rearrange your plt. calls:
plt.tight_layout(pad=0.9, w_pad=0.5, h_pad=0.1)
viz.hist(grid=True, rwidth = 0.9, color ='red', figsize=(15,10))
plt.show()

Plot secondary axis on multiple subplots in python

I am trying to plot on the secondary axis of all subplots of a bar chart, but I was only successful showing the secondary plot on one of the subplots (see image below).
I tried:
df[['loan_amnt','int_rate']].plot(kind='bar',subplots=True,layout=(1,2), figsize=(15,5))
df['dti'].plot(secondary_y=True, marker='d', style='g:');
and got see below:
What can I add to this code to ensure that the secondary plot is displayed on both subplots.
I was able to solve this using the code below:
fig = plt.figure(figsize=(15,5))
cx0 = fig.add_subplot(121)
cx1 = cx0.twinx()
cx2 = plt.subplot(122)
cx3 = cx2.twinx()
rate_amnt_byGrade['loan_amnt'].plot(kind='bar', ax=cx0)
rate_amnt_byGrade['dti'].plot(ax=cx1, secondary_y=True)
rate_amnt_byGrade['int_rate'].plot(kind='bar', ax=cx2)
rate_amnt_byGrade['dti'].plot(ax=cx3, secondary_y=True)

Resources