matplotlib: reduce empty space on axis - python

The following code (obtained from here):
import matplotlib.pyplot as plt
import pandas as pd
# Prepare Data
df = pd.read_csv("https://github.com/selva86/datasets/raw/master/mtcars.csv")
x = df.loc[:, ['mpg']]
df['mpg_z'] = (x - x.mean())/x.std()
df['colors'] = ['red' if x < 0 else 'darkgreen' for x in df['mpg_z']]
df.sort_values('mpg_z', inplace=True)
df.reset_index(inplace=True)
# Draw plot
plt.figure(figsize=(14,16), dpi= 80)
plt.scatter(df.mpg_z, df.index, s=450, alpha=.6, color=df.colors)
for x, y, tex in zip(df.mpg_z, df.index, df.mpg_z):
t = plt.text(x, y, round(tex, 1), horizontalalignment='center',
verticalalignment='center', fontdict={'color':'white'})
# Decorations
# Lighten borders
plt.gca().spines["top"].set_alpha(.3)
plt.gca().spines["bottom"].set_alpha(.3)
plt.gca().spines["right"].set_alpha(.3)
plt.gca().spines["left"].set_alpha(.3)
plt.yticks(df.index, df.cars)
plt.title('Diverging Dotplot of Car Mileage', fontdict={'size':20})
plt.xlabel('$Mileage$')
plt.grid(linestyle='--', alpha=0.5)
plt.xlim(-2.5, 2.5)
plt.show()
Gives me this:
What I'm trying to do is reduce the empty space on the y-axis, indicated here by the red bars:
How can I do this? Changing the height of the figure doesn't seem to help.

One quick solution is to manually set the margins using
plt.margins(y=0) # no margin at all
plt.margins(y=1/len(df)) # equal margin

Related

Change color of specific bar in matplotlib barplot

I want to change the color of a bar in matplotlib's grouped barplot if it meets a certain condition. I'm plotting two bars for each species - one for today and one for avg, where avg contains yerr errorbars that show the 10th and 90th percentile values.
Now I want the avg bar to be green if today's length value > 10th percentile, and red if today's length value < 10th percentile.
I tried the solutions in these posts
how to change the color of a single bar if condition is True matplotlib
Update Single Bar in Matplotlib
but the bars are always green.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
z = pd.DataFrame(data={'length': [40,35,34,40,36,39,38,44,40,39,35,46],
'species': ['A','A','A','A','B','B','B','B','C','C','C','C'],
'type': ['today','avg','10pc','90pc','today','avg','10pc','90pc','today','avg','10pc','90pc']
},
)
z['Date'] = pd.to_datetime('2021-09-20')
z.set_index('Date',inplace=True)
z0 = z.loc[(z.type=='today') | (z.type=='avg')] # average length and today's length
z1 = z.loc[(z.type=='10pc') | (z.type=='90pc')] # 10th and 90th percentile
z2 = []
for n in z.species.unique().tolist():
dz = z.loc[(z.species==n) & (z.type=='today'),'length'].values[0] - z.loc[(z.species==n) & (z.type=='10pc'),'length'].values[0]
if dw>0:
z2.append(1)
else:
z2.append(0)
errors = z1.pivot_table(columns=[z1.index,'species'],index='type',values=['length']).values
avgs = z0.length[z0.type=='avg'].values
bars = np.stack((np.absolute(errors-avgs), np.zeros([2,z1.species.unique().size])), axis=0)
col = ['pink']
for k in z2:
if k==1:
col.append('g') # length within 10% bounds = green
else:
col.append('r') # length outside 10% bounds = red
fig, ax = plt.subplots()
z0.pivot(index='species', columns='type', values='length').plot(kind='bar', yerr=bars, ax=ax, color=col, capsize=0)
ax.set_title(z0.index[0].strftime('%d %b %Y'), fontsize=16)
ax.set_xlabel('species', fontsize=14)
ax.set_ylabel('length (cm)', fontsize=14)
plt.show()
One way is to overwrite the colors after creating the plot. First you need to change the line that initialize col with
col = ['pink']*z['species'].nunique()
to get the numbers of avg bars, then the same for loop to add g or r depending on your case. Finally, change this
fig, ax = plt.subplots()
z0.pivot(index='species', columns='type', values='length')\
.plot(kind='bar', yerr=bars, ax=ax,
color=['pink','g'], capsize=0) # here use only pink and g
# here overwrite the colors
for p, c in zip(ax.patches, col):
p.set_color(c)
ax.set_title...
Note that the legend for today is green even if you have a red bar, could be confusing.
Here is the full working example, adding the red entry in the legend thanks to this answer
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches # import this extra
z = pd.DataFrame(data={'length': [40,35,34,40,36,39,38,44,40,39,35,46],
'species': ['A','A','A','A','B','B','B','B','C','C','C','C'],
'type': ['today','avg','10pc','90pc','today','avg','10pc','90pc','today','avg','10pc','90pc']
},
)
z['Date'] = pd.to_datetime('2021-09-20')
z.set_index('Date',inplace=True)
z0 = z.loc[(z.type=='today') | (z.type=='avg')] # average length and today's length
z1 = z.loc[(z.type=='10pc') | (z.type=='90pc')] # 10th and 90th percentile
z2 = []
for n in z.species.unique().tolist():
dz = z.loc[(z.species==n) & (z.type=='today'),'length'].values[0] - z.loc[(z.species==n) & (z.type=='10pc'),'length'].values[0]
if dz>0:
z2.append(1)
else:
z2.append(0)
errors = z1.pivot_table(columns=[z1.index,'species'],index='type',values=['length']).values
avgs = z0.length[z0.type=='avg'].values
bars = np.stack((np.absolute(errors-avgs), np.zeros([2,z1.species.unique().size])), axis=0)
col = ['pink']*z['species'].nunique()
for k in z2:
if k==1:
col.append('g') # length within 10% bounds = green
else:
col.append('r') # length outside 10% bounds = red
print(col)
# ['pink', 'pink', 'pink', 'g', 'r', 'g']
fig, ax = plt.subplots()
z0.pivot(index='species', columns='type', values='length').plot(kind='bar', yerr=bars, ax=ax, color=['pink','g'], capsize=0)
for p, c in zip(ax.patches, col):
p.set_color(c)
ax.set_title(z0.index[0].strftime('%d %b %Y'), fontsize=16)
ax.set_xlabel('species', fontsize=14)
ax.set_ylabel('length (cm)', fontsize=14)
handles = None
labels = None
if 0 in z2: ## add the entry on the legend only when there is red bar
# where some data has already been plotted to ax
handles, labels = ax.get_legend_handles_labels()
# manually define a new patch
patch = mpatches.Patch(color='r', label='today')
# handles is a list, so append manual patch
handles.append(patch)
# plot the legend
plt.legend(handles=handles)
else:
# plot the legend when there isn't red bar
plt.legend(handles=handles)
plt.show()
and I get the red bar

two DataFrame plots

I have a similar plot to the one answered in the link below:
two DataFrame plot in a single plot matplotlip
I made some modification to plots for df2 columns code block because i think that is where i have to modify but i could not yield the output.
a sample of the plot i want is this
this was how i modified it:
f, axes = plt.subplots(nrows=len(signals.columns)+1, sharex=True, )
i = 0
for col in df2.columns:
fig, axs = plt.subplots()
sns.regplot(x='', y='', data=df2, ax=axs[0])
df2[col].plot(ax=axes[i], color='grey')
axes[i].set_ylabel(col)
i+=1
I have seen that its wrong.
I tried this out, it seems like a head way :)
How do I make modification on this to get what i want:
f, axes = plt.subplots(nrows=len(signals.columns)+1, sharex=True, )
# plots for df2 columns
i = 0
for col in df2.columns:
lw=1
df2[col].plot(ax=axes[i], color='grey')
axes[i].set_ylim(0, 1)
axes[i].set_ylabel(col)
sns.rugplot(df2["P1"])
You have several options to make this graph. df1 and df2 are as defined in your previous question
The version with matplotlib.pyplot.scatter is faster to draw, but less faithful to the example. The version with seaborn.rugplot looks identical to the example, but takes longer to draw. I highlighted the important part of the code between comment lines ########
using matplotlib.pyplot.scatter
import seaborn as sns
import numpy as np
f, axes = plt.subplots(nrows=len(df2.columns)+1, sharex=True,
gridspec_kw={'height_ratios':np.append(np.repeat(1, len(df2.columns)), 3)})
####### variable part below #######
# plots for df2 columns
i = 0
for col in df2.columns:
axes[i].scatter(x=df2.index, y=np.repeat(0, len(df2)), c=df2[col], marker='|', cmap='Greys')
axes[i].set_ylim(-0.5, 0.5)
axes[i].set_yticks([0])
axes[i].set_yticklabels([col])
i+=1
###################################
## code to plot annotations
axes[-1].set_xlabel('Genomic position')
axes[-1].set_ylabel('annotations')
axes[-1].set_ylim(-0.5, 1.5)
axes[-1].set_yticks([0, 1])
axes[-1].set_yticklabels(['−', '+'])
for _, r in df1.iterrows():
marker = '|'
lw=1
if r['type'] == 'exon':
marker=None
lw=8
y = 1 if r['strand'] == '+' else 0
axes[-1].plot((r['start'], r['stop']), (y, y),
marker=marker, lw=lw,
solid_capstyle='butt',
color='#505050')
# remove space between plots
plt.subplots_adjust(hspace=0)
axes[-1].set_xlim(0, len(df2))
f.set_size_inches(6, 2)
using seaborn.rugplot
import seaborn as sns
import numpy as np
f, axes = plt.subplots(nrows=len(df2.columns)+1, sharex=True,
gridspec_kw={'height_ratios':np.append(np.repeat(1, len(df2.columns)), 3)})
####### variable part below #######
import matplotlib
import matplotlib.cm as cm
norm = matplotlib.colors.Normalize(vmin=0, vmax=1, clip=True)
mapper = cm.ScalarMappable(norm=norm, cmap=cm.Greys)
# plots for df2 columns
i = 0
for col in df2.columns:
sns.rugplot(x=df2.index, color=list(map(mapper.to_rgba, df2[col])), height=1, ax=axes[i])
axes[i].set_yticks([0])
axes[i].set_yticklabels([col])
i+=1
###################################
## code to plot annotations
axes[-1].set_xlabel('Genomic position')
axes[-1].set_ylabel('annotations')
axes[-1].set_ylim(-0.5, 1.5)
axes[-1].set_yticks([0, 1])
axes[-1].set_yticklabels(['−', '+'])
for _, r in df1.iterrows():
marker = '|'
lw=1
if r['type'] == 'exon':
marker=None
lw=8
y = 1 if r['strand'] == '+' else 0
axes[-1].plot((r['start'], r['stop']), (y, y),
marker=marker, lw=lw,
solid_capstyle='butt',
color='#505050')
# remove space between plots
plt.subplots_adjust(hspace=0)
axes[-1].set_xlim(0, len(df2))
f.set_size_inches(6, 2)

Matplotlib Draw a Constant y Axis

I want to use matpoltlib to make a plot that with a constant y axis(always from 0 to 14 and the gap is 1), since I want to make labels for them and my dot values will be(x, y) where y is from 0 to 14 gap 1, and a changing x axis. I already tried to play with y ticks. And here is my code for that:
fig, ax = plt.subplots()
fig.canvas.draw()
plt.yticks(np.arange(0, 14, 1))
labels = [item.get_text() for item in ax.get_yticklabels()]
labels[1] = 'Not Detected'
labels[2] = 'A/G'
labels[3] = 'G/G'
labels[4] = 'C/T'
labels[5] = 'C/C'
labels[6] = 'A/A'
labels[7] = '-1'
labels[8] = 'ε3/ε3'
labels[9] = 'A/C'
labels[10] = 'T/T'
labels[11] = 'C/G'
labels[12] = 'ε2/ε3'
labels[13] = 'G/T'
ax.set_yticklabels(labels)
what I'm thinking about is to use some values or lines with white color so those y axis will appear. But I'm looking for a more efficient way of doing it. And here is the diagram I generated with the current code. It only shows C/C right now and I want all labels to appear in the diagram.
I tried draw white points with:
x1 = np.arange(n)
y1 = np.arange(1,15,1)
plt.scatter(x1,y1,color = 'white')
Which did give me what I want: But I was wondering whether there is a lib setting that can do this.
I would recommend just using a fixed locator and fixed formatter for your y axis. The function, ax.set_yticklabels() is simply a convenience wrapper for these tick methods.
I would also recommend having your y_labels in a list or using a loop structure as this is a more generalizable and modifiable implementation.
If I'm understanding the goals of your plot correctly, something like this may work well for you.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
#make some data
x = np.arange(25)
y = np.random.randint(1, 14, size=25)
#convert y labels to a list
y_labels = [
'Not Detected','A/G','G/G','C/T','C/C','A/A',
'-1','ε3/ε3', 'A/C','T/T','C/G','ε2/ε3','G/T'
]
#define figure/ax and set figsize
fig, ax = plt.subplots(figsize=(12,8))
#plot data, s is marker size, it's points squared
ax.scatter(x, y, marker='x', s=10**2, color='#5d2287', linewidth=2)
#set major locator and formatter to fixed, add grid, hide top/right spines
locator = ax.yaxis.set_major_locator(mpl.ticker.FixedLocator(np.arange(1, 14)))
formatter = ax.yaxis.set_major_formatter(mpl.ticker.FixedFormatter(y_labels))
grid = ax.grid(axis='y', dashes=(8,3), alpha=0.3, color='gray')
spines = [ax.spines[x].set_visible(False) for x in ['top','right']]
params = ax.tick_params(labelsize=12) #increase label font size

Python matplotlib polar coordinate is not plotting as it is supposed to be

I am plotting from a CSV file that contains Cartesian coordinates and I want to change it to Polar coordinates, then plot using the Polar coordinates.
Here is the code
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import seaborn as sns
df = pd.read_csv('test_for_plotting.csv',index_col = 0)
x_temp = df['x'].values
y_temp = df['y'].values
df['radius'] = np.sqrt( np.power(x_temp,2) + np.power(y_temp,2) )
df['theta'] = np.arctan2(y_temp,x_temp)
df['degrees'] = np.degrees(df['theta'].values)
df['radians'] = np.radians(df['degrees'].values)
ax = plt.axes(polar = True)
ax.set_aspect('equal')
ax.axis("off")
sns.set(rc={'axes.facecolor':'white', 'figure.facecolor':'white','figure.figsize':(10,10)})
# sns.scatterplot(data = df, x = 'x',y = 'y', s= 1,alpha = 0.1, color = 'black',ax = ax)
sns.scatterplot(data = df, x = 'radians',y = 'radius', s= 1,alpha = 0.1, color = 'black',ax = ax)
plt.tight_layout()
plt.show()
Here is the dataset
If you run this command using polar = False and use this line to plot sns.scatterplot(data = df, x = 'x',y = 'y', s= 1,alpha = 0.1, color = 'black',ax = ax) it will result in this picture
now after setting polar = True and run this line to plot sns.scatterplot(data = df, x = 'radians',y = 'radius', s= 1,alpha = 0.1, color = 'black',ax = ax) It is supposed to give you this
But it is not working as if you run the actual code the shape in the Polar format is the same as Cartesian which does not make sense and it does not match the picture I showed you for polar (If you are wondering where did I get the second picture from, I plotted it using R)
I would appreciate your help and insights and thanks in advance!
For a polar plot, the "x-axis" represents the angle in radians. So, you need to switch x and y, and convert the angles to radians (I also added ax=ax, as the axes was created explicitly):
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import seaborn as sns
data = {'radius': [0, 0.5, 1, 1.5, 2, 2.5], 'degrees': [0, 25, 75, 155, 245, 335]}
df_temp = pd.DataFrame(data)
ax = plt.axes(polar=True)
sns.scatterplot(x=np.radians(df_temp['degrees']), y=df_temp['radius'].to_numpy(),
s=100, alpha=1, color='black', ax=ax)
for deg, y in zip(df_temp['degrees'], df_temp['radius']):
x = np.radians(deg)
ax.axvline(x, color='skyblue', ls=':')
ax.text(x, y, f' {deg}', color='crimson')
ax.set_rlabel_position(-15) # Move radial labels away from plotted dots
plt.tight_layout()
plt.show()
About your new question: if you have an xy plot, and you convert these xy values to polar coordinates, and then plot these on a polar plot, you'll get again the same plot.
After some more testing with the data, I decided to create the plot directly with matplotlib, as seaborn makes some changes that don't have exactly equal effects across seaborn and matplotlib versions.
What seems to be happening in R:
The angles (given by "x") are spread out to fill the range (0,2 pi). This either requires a rescaling of x, or change how the x-values are mapped to angles. One way to get this, is subtracting the minimum. And with that result divide by the new maximum and multiply by 2 pi.
The 0 of the angles it at the top, and the angles go clockwise.
The following code should create the plot with Python. You might want to experiment with alpha and with s in the scatter plot options. (Default the scatter dots get an outline, which often isn't desired when working with very small dots, and can be removed by lw=0.)
ax = plt.axes(polar=True)
ax.set_aspect('equal')
ax.axis('off')
x_temp = df['x'].to_numpy()
y_temp = df['y'].to_numpy()
x_temp -= x_temp.min()
x_temp = x_temp / x_temp.max() * 2 * np.pi
ax.scatter(x=x_temp, y=y_temp, s=0.05, alpha=1, color='black', lw=0)
ax.set_rlim(y_temp.min(), y_temp.max())
ax.set_theta_zero_location("N") # set zero at the north (top)
ax.set_theta_direction(-1) # go clockwise
plt.show()
At the left the resulting image, at the right using the y-values for coloring (ax.scatter(..., c=y_temp, s=0.05, alpha=1, cmap='plasma_r', lw=0)):

Add Datalabel on the end of barchart (lollypop)

I'm totally new at using Python for Power BI (or anything really).
I would like to add the value of the bar/scatter at the end of the line. (the datalabel)
Also to have a version where I could have the label inside of the scatter bubble would be cool.
Anyone who could help out here ?
All help appreciated
# libraries
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# Create a dataframe
df = pd.DataFrame({'group': dataset.Genre , 'values': dataset.Revenue})
val = list(dataset.SelectedGenre)
# Reorder it following the values:
ordered_df = df.sort_values(by='values')
my_range=range(1,len(df.index)+1)
# Create a color if the group is "B"
my_color=np.where(ordered_df ['group']== val, 'orange', 'skyblue')
my_size=np.where(ordered_df ['group']== val , 150, 150)
# The vertival plot is made using the hline function
# I load the seaborn library only to benefit the nice looking feature
import seaborn as sns
val = ordered_df['values']
plt.hlines(y=my_range, xmin=0, xmax=val, color=my_color, alpha=1 , linewidth=8)
plt.scatter(val, my_range, color=my_color, s=my_size, alpha=1)
# Add title and axis names
plt.yticks(my_range, ordered_df['group'])
plt.title("What about the B group?", loc='left')
plt.xlabel('Value of the variable')
plt.ylabel('Group')
plt.box(False) #Turn of Black bx around visual
plt.show()
Found it myself
import matplotlib.pyplot as plt
import numpy as np
# Data
x = dataset.Revenue
y = dataset.Genre
labels = dataset.Revenue
val = list(dataset.SelectedGenre)
# Create the figure and axes objects
fig, ax = plt.subplots(1, figsize=(10, 6))
fig.suptitle('Example Of Labelled Scatterpoints')
my_color=np.where(y == val, 'orange', 'skyblue')
my_size=np.where( y == val , 2000, 2000)
# Plot the scatter points
ax.scatter(x, y,
color= my_color, # Color of the dots
s=1000, # Size of the dots
alpha=1, # Alpha of the dots
linewidths=1) # Size of edge around the dots
ax.hlines(y, xmin=0, xmax=x, color= my_color, alpha=1 , linewidth=8)
def human_format(num):
magnitude = 0
while abs(num) >= 1000:
magnitude += 1
num /= 1000
# add more suffixes if you need them
return '%.0f%s' % (round(num), ['', 'K', 'M', 'G', 'T', 'P'][magnitude])
# Add the participant names as text labels for each point
for x_pos, y_pos, label in zip(x, y, labels):
ax.annotate(
human_format(label), # The label for this point
xy=(x_pos, y_pos), # Position of the corresponding point
xytext=(-8, 0), # Offset text by 7 points to the right
textcoords='offset points', # tell it to use offset points
ha='left', # Horizontally aligned to the left
va='center',
color = 'white') # Vertical alignment is centered
plt.box(False) #Turn of Black bx around visual
# Show the plot
plt.show()

Categories

Resources