Matplotlib twin y axis - python

I want to plot my data with 0 at the middle of y axis. Just like this:
This is what I came up with:
Using this code:
import matplotlib.pyplot as plt
group_a_names = ['A', 'B', 'C', 'D', 'E']
group_a_values = [2, 4, 6, 8, 10]
group_b_names = ['F', 'G', 'H', 'I', 'J']
group_b_values = [1, 2, 3, 4, 5]
fig, ax1 = plt.subplots(figsize=(5, 4), dpi=100)
ax2 = ax1.twiny()
ax1.plot(group_a_names, group_a_values)
ax2.plot(group_b_names, group_b_values)
plt.show()
How can I visualize my data just like the first image? Also mirror the y tick labels/marks on the right side?

Try this:
import matplotlib.pyplot as plt
group_a_names = ['A', 'B', 'C', 'D', 'E']
group_a_values = [2, 4, 6, 8, 10]
group_b_names = ['F', 'G', 'H', 'I', 'J']
group_b_values = [-2, -4, -6, -8, -10]
fig, ax1 = plt.subplots(figsize=(5, 4), dpi=100)
ax1.plot(group_a_names, group_a_values)
# add second x axis
ax3 = ax1.twiny()
ax3.plot(group_b_names, group_b_values)
# add second y axis
ax2 = ax1.twinx()
# set y axis range
plt.ylim(-10, 10)
plt.show()
Result:

This worked for me:
ticks = np.arange(2, 11, 2)
plt.yticks(ticks, [10, 5, 0, 5, 10])
ax1.yaxis.set_ticks_position('both')
ax1.tick_params(axis="y", labelright=True)

I just flipped the other values and flip back the negative labels.
import matplotlib.pyplot as plt
group_a_names = ['A', 'B', 'C', 'D', 'E']
group_a_values = [2, 4, 6, 8, 10]
group_b_names = ['F', 'G', 'H', 'I', 'J']
group_b_values = [1, 2, 3, 4, 5]
group_b_values_neg = list(map(lambda n: n * -1, group_b_values))
max_value = max(group_a_values + group_b_values)
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
ax2 = ax1.twiny()
ax1.plot(group_a_names, group_a_values, c="blue")
ax2.plot(group_b_names, group_b_values_neg, c="red")
ax1.set_ylim(max_value * -1, max_value)
ax2.set_ylim(max_value * -1, max_value)
ax2.set_yticklabels([str(abs(x)) for x in ax2.get_yticks()])
ax1.yaxis.set_ticks_position('both')
ax1.tick_params(axis="y", labelright=True)
plt.show()

Related

How to create a Crosstab Plot?

I would like to create a 'Crosstab' plot like the below using matplotlib or seaborn:
Using the following dataframe:
import pandas as pd
data = [['A', 'C', 2], ['A', 'D', 8], ['B', 'C', 25], ['B', 'D', 30]]
df = pd.DataFrame(data = data, columns = ['col', 'row', 'val'])
col row val
0 A C 2
1 A D 8
2 B C 25
3 B D 30
An option in matplotlib could be by adding Rectangles to the origin via plt.gca and add_patch. The problem is that I did here all manually like this:
from matplotlib.patches import Rectangle
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])
plt.xlim(-10, 40)
plt.ylim(-40, 40)
plt.rcParams['figure.figsize'] = (10,16)
someX, someY = 0, 0
currentAxis = plt.gca()
currentAxis.add_patch(Rectangle((someX, someY), 30, 30, facecolor="purple"))
ax.text(15, 15, '30')
currentAxis.add_patch(Rectangle((someX, someY), 25, -25, facecolor="blue"))
ax.text(12.5, -12.5, '25')
currentAxis.add_patch(Rectangle((someX, someY), -2, -2, facecolor="red"))
ax.text(-1, -1, '2')
currentAxis.add_patch(Rectangle((someX, someY), -8, 8, facecolor="green"))
ax.text(-4, 4, '8')
Output:
As you can see, the plot doesn't look that nice. So I was wondering if it is possible to somehow automatically create 'Crosstab' plots using matplotlib or seaborn?
I am not sure whether matplotlib or seaborn have dedicated functions for this type of plot or not, but using plt.bar and plt.bar_label instead of Rectangle and plt.Text might help automatize things a little (label placement etc.).
See code below:
import matplotlib.pyplot as plt
data = [['A', 'C', 2], ['A', 'D', 8], ['B', 'C', 25], ['B', 'D', 30]]
pos={'A':-1,'B':0,'C':-1,'D':1}
fig,ax=plt.subplots(figsize=(10,10))
p=[ax.bar(pos[d[0]]*d[2],pos[d[1]]*d[2],width=d[2],align='edge') for d in data]
[ax.bar_label(p[i],labels=[data[i][2]], label_type='center',fontsize=18) for i in range(len(data))]
ax.set_aspect('equal')

Separate bar in chart

I would like to show in every bin of the histogram, the 3 bars separated, so that it does not overlap. My code is this:
face = io.imread('images/face.png')
red_chanel = face[:,:,0]
green_chanel = face[:,:,1]
blue_chanel = face[:,:,2]
red_chanel = red_chanel.astype('float')
green_chanel = green_chanel.astype('float')
blue_chanel = blue_chanel.astype('float')
face = face.astype('float')
fig, ax1 = plt.subplots(ncols = 1, figsize = (20, 5))
hstred=exposure.histogram(red_chanel, nbins=28)
hstgreen=exposure.histogram(green_chanel, nbins=28)
hstblue=exposure.histogram(blue_chanel, nbins=28)
ax1.bar(list(range(28)), hstred[0], align='edge')
ax1.bar(list(range(28)), hstgreen[0], align='edge')
ax1.bar(list(range(28)), hstblue[0], align='edge')
plt.show()
How can I separate the bars?
I think you can shift the x-axis for 2nd and 3rd barplot and play with bar width a little. In the end, change the xticks.
import numpy as np
ax1.bar(np.arange(28), hstred[0], align='edge', width=0.3)
#shifting the xaxis
ax1.bar(np.arange(28)+0.3, hstgreen[0], align='edge', width=0.3)
ax1.bar(np.arange(28)+0.6, hstblue[0], align='edge', width=0.3)
plt.xticks(np.arange(0,28)+0.3, np.arange(0,28)) #resetting the ticks
Here is an example:
x1 = [1, 2, 3, 4, 5]
y1 = [1, 2, 3, 5, 6]
y2 = [4, 4, 2, 2, 2]
y3 = [3, 4, 6, 7, 8]
fig,ax = plt.subplots()
ax.bar(x1,y1,width=0.3)
ax.bar(np.array(x1)+0.3,y2,width=0.3)
ax.bar(np.array(x1)+0.6,y3,width=0.3)
plt.xticks(np.arange(0,6)+0.3, np.arange(0,6))
plt.show()
Output:

Using condition to label selection of points based on a value

How can I label only the points where X >= 3? I don't see any points labelled with this output.
This is very similar to the simple labelled points example but I feel like I am missing something simple.
import altair as alt
import pandas as pd
source = pd.DataFrame({
'x': [1, 3, 5, 7, 9],
'y': [1, 3, 5, 7, 9],
'label': ['A', 'B', 'C', 'D', 'E']
})
points = alt.Chart(source).mark_point().encode(
x='x:Q',
y='y:Q'
)
text = points.mark_text(
align='left',
baseline='middle',
dx=7
).encode(
text=alt.condition(alt.FieldGTEPredicate('x:Q', 3), 'label', alt.value(' '))
)
points + text
Predicates do not recognize encoding type shorthands; you should use the field name directly:
text=alt.condition(alt.FieldGTEPredicate('x', 3), 'label', alt.value(' '))
Even better, since this is essentially a filter operation, is to use a filter transform in place of the conditional value:
import altair as alt
import pandas as pd
source = pd.DataFrame({
'x': [1, 3, 5, 7, 9],
'y': [1, 3, 5, 7, 9],
'label': ['A', 'B', 'C', 'D', 'E']
})
points = alt.Chart(source).mark_point().encode(
x='x:Q',
y='y:Q'
)
text = points.transform_filter(
alt.datum.x >= 3
).mark_text(
align='left',
baseline='middle',
dx=7
).encode(
text='label'
)
points + text

bar chart legend based on coloring of bars by group not value

I've created a bar chart as described here where I have multiple variables (indicated in the 'value' column) and they belong to repeat groups. I've colored the bars by their group membership.
I want to create a legend ultimately equivalent to the colors dictionary, showing the color corresponding to a given group membership.
Code here:
d = {'value': [1, 2, 4, 5, 7 ,10], 'group': [1, 2, 3, 2, 2, 3]}
df = pd.DataFrame(data=d)
colors = {1: 'r', 2: 'b', 3: 'g'}
df['value'].plot(kind='bar', color=[colors[i] for i in df['group']])
plt.legend(df['group'])
In this way, I just get a legend with one color (1) instead of (1, 2, 3).
Thanks!
You can use sns:
sns.barplot(data=df, x=df.index, y='value',
hue='group', palette=colors, dodge=False)
Output:
With pandas, you could create your own legend as follows:
from matplotlib import pyplot as plt
from matplotlib import patches as mpatches
import pandas as pd
d = {'value': [1, 2, 4, 5, 7 ,10], 'group': [1, 2, 3, 2, 2, 3]}
df = pd.DataFrame(data=d)
colors = {1: 'r', 2: 'b', 3: 'g'}
df['value'].plot(kind='bar', color=[colors[i] for i in df['group']])
handles = [mpatches.Patch(color=colors[i]) for i in colors]
labels = [f'group {i}' for i in colors]
plt.legend(handles, labels)
plt.show()

Plot multiple stacked bar in the same figure

i would like to multiple stacked bar in the same plot. This is my code:
file_to_plot = file_to_plot.set_index(['user'])
fig, ax = plt.subplots()
fontP = FontProperties()
fontP.set_size('small')
file_to_plot[[" mean_accuracy_all_classes_normal", " delta_all_classes"]].plot(ax=ax, kind='bar', color= ['g', 'r'], width = 0.65, align="center", stacked=True)
file_to_plot[[" mean_accuracy_user_classes_normal", " delta_user_classes"]].plot(ax=ax, kind='bar', color=['y', 'b'], width=0.65, align="center", stacked = True)
lgd = ax.legend(['Tutte le classi (normale)', 'Tutte le classi (incrementale)', 'Classi utente (normale)', 'Classi utente (incrementale)'], prop=fontP, loc=9, bbox_to_anchor=(0.5, -0.15), ncol=4,borderaxespad=0.)
ax.set_ylabel('% Accuratezza')
ax.set_xlabel('Utenti')
This is the results:
The second plot overwhelms me when I want to plot them together. How can I do?
This should work the way you want:
import pandas as pd
df = pd.DataFrame(dict(
A=[1, 2, 3, 4],
B=[2, 3, 4, 5],
C=[3, 4, 5, 6],
D=[4, 5, 6, 7]))
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure(figsize=(20, 10))
ab_bar_list = [plt.bar([0, 1, 2, 3], df.B, align='edge', width= 0.2),
plt.bar([0, 1, 2, 3], df.A, align='edge', width= 0.2)]
cd_bar_list = [plt.bar([0, 1, 2, 3], df.D, align='edge',width= -0.2),
plt.bar([0, 1, 2, 3], df.C, align='edge',width= -0.2)]
Just keep in mind, the width value for one group must be positive, and negative for the second one. Use align by edge as well.
You have to place the bar with the biggest values before the bar with the lowest values, and if you want the bars to appear stacked above one another rather than one in front of another, change df.B and df.D to df.B + df.A and df.D + df.C, respectively. If there's no apparent or consisting pattern, use the align by edge and width method with the one suggested by #piRSquared.
Another alternative would be to access each value from a green bar and compare it to the corresponding value from the red bar, and plot accordingly (too much unnecessary work in this one).
I thought this would be straightforward. Hopefully someone else will chime in with a better solution. What I did was to take the diff's of the columns and run a stacked chart.
df = pd.DataFrame(dict(
A=[1, 2, 3, 4],
B=[2, 3, 4, 5],
C=[3, 4, 5, 6]
))
df.diff(axis=1).fillna(df).astype(df.dtypes).plot.bar(stacked=True)
For comparison
fig, axes = plt.subplots(1, 2, figsize=(10, 4), sharey=True)
df.plot.bar(ax=axes[0])
df.diff(axis=1).fillna(df).astype(df.dtypes).plot.bar(ax=axes[1], stacked=True)
there is in fact a direct way of stacking the bars via the bottom keyword
(if you plot a horizontal barplot with plt.barh use left instead of bottom)!
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame(dict(A=[1, 2, 3, 4], B=[2, 3, 4, 5], C=[3, 4, 5, 6]))
df2 = df / 2
f, ax = plt.subplots()
ax.bar(df.index, df.A, align='edge', width=0.2)
ax.bar(df.index, df.B, align='edge', width=0.2, bottom=df.A)
ax.bar(df.index, df.C, align='edge', width=0.2, bottom=df.A + df.B)
ax.bar(df2.index, df2.A, align='edge', width=-0.2)
ax.bar(df2.index, df2.B, align='edge', width=-0.2, bottom=df2.A)
ax.bar(df2.index, df2.C, align='edge', width=-0.2, bottom=df2.A + df2.B)
I used numpy to add the arrays together. Not sure if its exactly what you wanted, but its what I needed when I stumbled on this question. Thought it might help others.
import matplotlib.pyplot as plt
import numpy as np
dates = ['22/10/21', '23/10/21', '24/10/21', '25/10/21', '26/10/21']
z1 = np.array([20, 35, 30, 35, 27])
z2 = np.array([25, 32, 34, 20, 25])
z3 = np.array([20, 35, 30, 35, 27])
z4 = np.array([25, 32, 34, 20, 25])
z5 = np.array([20, 35, 30, 35, 27])
width = 0.35 # the width of the bars: can also be len(x) sequence
fig, ax = plt.subplots()
ax.bar(dates, z1, width, color='0.8', label='Z1')
ax.bar(dates, z2, width, color='b', label='Z2',bottom=z1)
ax.bar(dates, z3, width, color='g', label='Z3',bottom=z1 + z2)
ax.bar(dates, z4, width, color='tab:orange', label='Z4',bottom=z1 + z2 + z3)
ax.bar(dates, z5, width, color='r', bottom=z1 + z2 + z3 + z4,
label='Z5')
ax.set_ylabel('Time in HR Zones')
ax.set_title('HR Zones')
ax.legend()
plt.show()
Stacked Bar Graph

Categories