Seaborn: Plotting 2 pairplots side by side - python

I'm trying to make a function that produces two pairplots side by side. I've got it to produce 1 at time.
import seaborn as sns
data = sns.load_dataset("iris")
def pairplot(col,hue_var):
sns.set(style="ticks")
cols_to_look_at = col + [hue_var]
sns.pairplot(data[cols_to_look_at], hue=hue_var)
col_names1 = ['sepal_length', 'sepal_width']
col_names2 = ['petal_length', 'petal_width']
pairplot(col_names1,'species')
pairplot(col_names2,'species')
grateful for any help

In fact it is not a trivial matter.
pairplot has not ax parameter to make it simple due to its nature of "figure-level" at seaborn.
There are some hacks:
How to plot multiple Seaborn Jointplot in Subplot
A "clean" one consists in saving the plots as images to bring them back to place them: https://stackoverflow.com/a/61330067/10372616
Using it in your plots:
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
import seaborn as sns
data = sns.load_dataset("iris")
def pairplot(col,hue_var):
sns.set(style="ticks")
cols_to_look_at = col + [hue_var]
return sns.pairplot(data[cols_to_look_at], hue=hue_var) # !!! I've placed a return
col_names1 = ['sepal_length', 'sepal_width']
col_names2 = ['petal_length', 'petal_width']
g0 = pairplot(col_names1,'species')
g1 = pairplot(col_names2,'species')
############### 1. SAVE PLOTS IN MEMORY TEMPORALLY
g0.savefig('g0.png', dpi=300)
plt.close(g0.fig)
g1.savefig('g1.png', dpi=300)
plt.close(g1.fig)
############### 2. CREATE YOUR SUBPLOTS FROM TEMPORAL IMAGES
f, axarr = plt.subplots(1, 2, figsize=(20, 20))
axarr[0].imshow(mpimg.imread('g0.png'))
axarr[1].imshow(mpimg.imread('g1.png'))
# turn off x and y axis
[ax.set_axis_off() for ax in axarr.ravel()]
plt.tight_layout()
plt.show()

Related

How do I plot multiple lines within the same graph and each represents targeted group by using matplotlib? [duplicate]

In Pandas, I am doing:
bp = p_df.groupby('class').plot(kind='kde')
p_df is a dataframe object.
However, this is producing two plots, one for each class.
How do I force one plot with both classes in the same plot?
Version 1:
You can create your axis, and then use the ax keyword of DataFrameGroupBy.plot to add everything to these axes:
import matplotlib.pyplot as plt
p_df = pd.DataFrame({"class": [1,1,2,2,1], "a": [2,3,2,3,2]})
fig, ax = plt.subplots(figsize=(8,6))
bp = p_df.groupby('class').plot(kind='kde', ax=ax)
This is the result:
Unfortunately, the labeling of the legend does not make too much sense here.
Version 2:
Another way would be to loop through the groups and plot the curves manually:
classes = ["class 1"] * 5 + ["class 2"] * 5
vals = [1,3,5,1,3] + [2,6,7,5,2]
p_df = pd.DataFrame({"class": classes, "vals": vals})
fig, ax = plt.subplots(figsize=(8,6))
for label, df in p_df.groupby('class'):
df.vals.plot(kind="kde", ax=ax, label=label)
plt.legend()
This way you can easily control the legend. This is the result:
import matplotlib.pyplot as plt
p_df.groupby('class').plot(kind='kde', ax=plt.gca())
Another approach would be using seaborn module. This would plot the two density estimates on the same axes without specifying a variable to hold the axes as follows (using some data frame setup from the other answer):
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
# data to create an example data frame
classes = ["c1"] * 5 + ["c2"] * 5
vals = [1,3,5,1,3] + [2,6,7,5,2]
# the data frame
df = pd.DataFrame({"cls": classes, "indices":idx, "vals": vals})
# this is to plot the kde
sns.kdeplot(df.vals[df.cls == "c1"],label='c1');
sns.kdeplot(df.vals[df.cls == "c2"],label='c2');
# beautifying the labels
plt.xlabel('value')
plt.ylabel('density')
plt.show()
This results in the following image.
There are two easy methods to plot each group in the same plot.
When using pandas.DataFrame.groupby, the column to be plotted, (e.g. the aggregation column) should be specified.
Use seaborn.kdeplot or seaborn.displot and specify the hue parameter
Using pandas v1.2.4, matplotlib 3.4.2, seaborn 0.11.1
The OP is specific to plotting the kde, but the steps are the same for many plot types (e.g. kind='line', sns.lineplot, etc.).
Imports and Sample Data
For the sample data, the groups are in the 'kind' column, and the kde of 'duration' will be plotted, ignoring 'waiting'.
import pandas as pd
import seaborn as sns
df = sns.load_dataset('geyser')
# display(df.head())
duration waiting kind
0 3.600 79 long
1 1.800 54 short
2 3.333 74 long
3 2.283 62 short
4 4.533 85 long
Plot with pandas.DataFrame.plot
Reshape the data using .groupby or .pivot
.groupby
Specify the aggregation column, ['duration'], and kind='kde'.
ax = df.groupby('kind')['duration'].plot(kind='kde', legend=True)
.pivot
ax = df.pivot(columns='kind', values='duration').plot(kind='kde')
Plot with seaborn.kdeplot
Specify hue='kind'
ax = sns.kdeplot(data=df, x='duration', hue='kind')
Plot with seaborn.displot
Specify hue='kind' and kind='kde'
fig = sns.displot(data=df, kind='kde', x='duration', hue='kind')
Plot
Maybe you can try this:
fig, ax = plt.subplots(figsize=(10,8))
classes = list(df.class.unique())
for c in classes:
df2 = data.loc[data['class'] == c]
df2.vals.plot(kind="kde", ax=ax, label=c)
plt.legend()

How to create a FacetGrid stacked barplot using Seaborn?

I am trying to plot a facet_grid with stacked bar charts inside.
I would like to use Seaborn. Its barplot function does not include a stacked argument.
I tried to use FacetGrid.map with a custom callable function.
import pandas as pd
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
def custom_stacked_barplot(col_day, col_time, col_total_bill, **kwargs):
dict_df={}
dict_df['day']=col_day
dict_df['time']=col_time
dict_df['total_bill']=col_total_bill
df_data_graph=pd.DataFrame(dict_df)
df = pd.crosstab(index=df_data_graph['time'], columns=tips['day'], values=tips['total_bill'], aggfunc=sum)
df.plot.bar(stacked=True)
tips=sns.load_dataset("tips")
g = sns.FacetGrid(tips, col='size', row='smoker')
g = g.map(custom_stacked_barplot, "day", 'time', 'total_bill')
However I get an empty canvas and stacked bar charts separately.
Empty canvas:
Graph1 apart:
Graph2:.
How can I fix this issue? Thanks for the help!
The simplest code to achive that result is this:
import seaborn as sns
import matplotlib.pyplot as plt
sns.set()
tips=sns.load_dataset("tips")
g = sns.FacetGrid(tips, col = 'size', row = 'smoker', hue = 'day')
g = (g.map(sns.barplot, 'time', 'total_bill', ci = None).add_legend())
plt.show()
which gives this result:
Your different mixes of APIs (pandas.DataFrame.plot) appears not to integrate with (seaborn.FacetGrid). Since stacked bar plots are not supported in seaborn plotting, consider developing your own version with matplotlib subplots by iterating across groupby levels:
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
def custom_stacked_barplot(t, sub_df, ax):
plot_df = pd.crosstab(index=sub_df["time"], columns=sub_df['day'],
values=sub_df['total_bill'], aggfunc=sum)
p = plot_df.plot(kind="bar", stacked=True, ax = ax,
title = " | ".join([str(i) for i in t]))
return p
tips = sns.load_dataset("tips")
g_dfs = tips.groupby(["smoker", "size"])
# INITIALIZE PLOT
# sns.set()
fig, axes = plt.subplots(nrows=2, ncols=int(len(g_dfs)/2)+1, figsize=(15,6))
# BUILD PLOTS ACROSS LEVELS
for ax, (i,g) in zip(axes.ravel(), sorted(g_dfs)):
custom_stacked_barplot(i, g, ax)
plt.tight_layout()
plt.show()
plt.clf()
plt.close()
And use seaborn.set to adjust theme and pallette:

swarmplot with hue affecting marker beyond color

I'm trying to make my swarmplot easier to read in black&white and for people that are color-blind, by having the hue affect not just the color but also another geometrical aspect of the marker.
MWE
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")
fig, ax = plt.subplots(1,1)
ax = sns.swarmplot(x="day", y="total_bill", hue="sex",data=tips,size=8,ax=ax)
plt.show()
Result
Desired Result (the left one)
I actually had thought of the same problem a while ago. I did not come up with the greatest of solutions, but I have a hack that works OK. Unfortunately, it's much easier to implement if you use dodge=True.
The idea is to collect the PathCollections objects created by swarmplot. If dodge=True then you'll get N_cat*N_hues+N_hues collections (the N_hues extras are used to create the legend). You can simply iterate through that list. Since we want all hues to be the same, we use a N_hues stride to get all the collections corresponding to each of the hues. After that, you are free to update the paths of that collection to whatever Path object you choose. Refer to the documentation for Path to learn how to create paths.
To simplify things, I created some dummy scatter plots before hands to get some premade Paths that I can use. Of course, any Path should be able to work.
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")
fig, ax = plt.subplots(1,1)
# dummy plots, just to get the Path objects
a = ax.scatter([1,2],[3,4], marker='s')
b = ax.scatter([1,2],[3,4], marker='^')
square_mk, = a.get_paths()
triangle_up_mk, = b.get_paths()
a.remove()
b.remove()
ax = sns.swarmplot(x="day", y="total_bill", hue="sex",data=tips,size=8,ax=ax, dodge=True)
N_hues = len(pd.unique(tips.sex))
c = ax.collections
for a in c[::N_hues]:
a.set_paths([triangle_up_mk])
for a in c[1::N_hues]:
a.set_paths([square_mk])
#update legend
ax.legend(c[-2:],pd.unique(tips.sex))
plt.show()
UPDATE A solution that "works" with dodge=False.
If you use dodge=False, then you'll get N+2 collections, one for each category, +2 for the legend. The problem is that all the different marker colors are jumbled up in these collections.
A possible, but ugly, solution is to loop through each element of the collection, and create an array of Path objects based one the color of each element.
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")
fig, ax = plt.subplots(1,1)
ax = sns.swarmplot(x="day", y="total_bill", hue="sex",data=tips,size=8,ax=ax, dodge=False)
collections = ax.collections
unique_colors = np.unique(collections[0].get_facecolors(), axis=0)
markers = [triangle_up_mk, square_mk] # this array must be at least as large as the number of unique colors
for collection in collections:
paths = []
for current_color in collection.get_facecolors():
for possible_marker,possible_color in zip(markers, unique_colors):
if np.array_equal(current_color,possible_color):
paths.append(possible_marker)
break
collection.set_paths(paths)
#update legend
ax.legend(collections[-2:],pd.unique(tips.sex))
plt.show()
The following would provide a hack which allows to easily achieve the desired different markers for swarmplots (or more generally any categorical scatter plots). It can be used as is, just copy it on top of existing plot scripts.
The idea is to link the color of a scatter point with a marker. E.g. any scatter point would get a marker from a specified list automatically. As a consequence this only works for plots with different colors.
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
############## Begin hack ##############
class CM():
def __init__(self, markers=["o"]):
self.marker = np.array(markers)
self.colors = []
def get_markers_for_colors(self, c):
for _co in c:
if not any((_co == x).all() for x in self.colors):
self.colors.append(_co)
ind = np.array([np.where((self.colors == row).all(axis=1)) \
for row in c]).flatten()
return self.marker[ind % len(self.marker)]
def get_legend_handles(self, **kwargs):
return [plt.Line2D([0],[0], ls="none", marker=m, color=c, mec="none", **kwargs) \
for m,c in zip(self.marker, self.colors)]
from matplotlib.axes._axes import Axes
import matplotlib.markers as mmarkers
cm = CM(plt.Line2D.filled_markers)
old_scatter = Axes.scatter
def new_scatter(self, *args, **kwargs):
sc = old_scatter(self, *args, **kwargs)
c = kwargs.get("c", None)
if isinstance(c, np.ndarray):
m = cm.get_markers_for_colors(c)
paths = []
for _m in m:
marker_obj = mmarkers.MarkerStyle(_m)
paths.append(marker_obj.get_path().transformed(
marker_obj.get_transform()))
sc.set_paths(paths)
return sc
Axes.scatter = new_scatter
############## End hack. ##############
# Copy and past to your file ##########
## Code ###
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")
fig, ax = plt.subplots(1,1)
## Optionally specify own markers:
#cm.marker = np.array(["^", "s"])
ax = sns.swarmplot(x="day", y="total_bill", hue="sex",data=tips,size=8,ax=ax)
## Optionally adjust legend:
_,l = ax.get_legend_handles_labels()
ax.legend(cm.get_legend_handles(markersize=8),l)
plt.show()
Thank you to #ImportanceOfBeingErnest for the solution. I tried to edit his/her solution to fix some minor issues but in the end he/she suggested that I post my own answer.
This solution is the same as his/hers but it doesn't change the behavior of normal scatter when the marker array is not specified. It is also simpler to apply and it fixes the error where the legend loses the title.
The following figure is produced by the code below:
import seaborn as sns
import matplotlib.pyplot as plt
############## Begin hack ##############
from matplotlib.axes._axes import Axes
from matplotlib.markers import MarkerStyle
from seaborn import color_palette
from numpy import ndarray
def GetColor2Marker(markers):
palette = color_palette()
mkcolors = [(palette[i]) for i in range(len(markers))]
return dict(zip(mkcolors,markers))
def fixlegend(ax,markers,markersize=8,**kwargs):
# Fix Legend
legtitle = ax.get_legend().get_title().get_text()
_,l = ax.get_legend_handles_labels()
palette = color_palette()
mkcolors = [(palette[i]) for i in range(len(markers))]
newHandles = [plt.Line2D([0],[0], ls="none", marker=m, color=c, mec="none", markersize=markersize,**kwargs) \
for m,c in zip(markers, mkcolors)]
ax.legend(newHandles,l)
leg = ax.get_legend()
leg.set_title(legtitle)
old_scatter = Axes.scatter
def new_scatter(self, *args, **kwargs):
colors = kwargs.get("c", None)
co2mk = kwargs.pop("co2mk",None)
FinalCollection = old_scatter(self, *args, **kwargs)
if co2mk is not None and isinstance(colors, ndarray):
Color2Marker = GetColor2Marker(co2mk)
paths=[]
for col in colors:
mk=Color2Marker[tuple(col)]
marker_obj = MarkerStyle(mk)
paths.append(marker_obj.get_path().transformed(marker_obj.get_transform()))
FinalCollection.set_paths(paths)
return FinalCollection
Axes.scatter = new_scatter
############## End hack. ##############
# Example Test
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")
# To test robustness
tips.loc[(tips['sex']=="Male") & (tips['day']=="Fri"),'sex']='Female'
tips.loc[(tips['sex']=="Female") & (tips['day']=="Sat"),'sex']='Male'
Markers = ["o","P"]
fig, axs = plt.subplots(1,2,figsize=(14,5))
axs[0] = sns.swarmplot(x="day", y="total_bill", hue="sex",data=tips,size=8,ax=axs[0])
axs[0].set_title("Original")
axs[1] = sns.swarmplot(x="day", y="total_bill", hue="sex",data=tips,size=8,ax=axs[1],co2mk=Markers)
axs[1].set_title("Hacked")
fixlegend(axs[1],Markers)
plt.show()

How to plot heat map in matplotlib with label at both side right and left

UPDATED
I have write down a code like the given bellow..
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
df = pd.read_csv("data_1.csv",index_col="Group")
print df
fig,ax = plt.subplots(1)
heatmap = ax.pcolor(df)########
ax.pcolor(df,edgecolors='k')
cbar = plt.colorbar(heatmap)##########
plt.ylim([0,12])
ax.invert_yaxis()
locs_y, labels_y = plt.yticks(np.arange(0.5, len(df.index), 1), df.index)
locs_x, labels_x = plt.xticks(np.arange(0.5, len(df.columns), 1), df.columns)
ax.set_xticklabels(labels_x, rotation=10)
ax.set_yticklabels(labels_y,fontsize=10)
plt.show()
Which takes input like given bellow and plot a heat map with the two side leabel left and bottom..
GP1,c1,c2,c3,c4,c5
S1,21,21,20,69,30
S2,28,20,20,39,25
S3,20,21,21,44,21
I further want to add additional labels at right side as given bellow to the data and want to plot a heatmap with three side label. right left and bottom.
GP1,c1,c2,c3,c4,c5
S1,21,21,20,69,30,V1
S2,28,20,20,39,25,V2
S3,20,21,21,44,21,V3
What changes should i incorporate into the code.
Please help ..
You may create a new axis on the right of the plot, called twinx. Then you need to essentially adjust this axis the same way you already did with the first axis.
u = u"""GP1,c1,c2,c3,c4,c5
S1,21,21,20,69,30
S2,28,20,20,39,25
S3,20,21,21,44,21"""
import io
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
df= pd.read_csv(io.StringIO(u),index_col="GP1")
fig,ax = plt.subplots(1)
heatmap = ax.pcolor(df, edgecolors='k')
cbar = plt.colorbar(heatmap, pad=0.1)
bx = ax.twinx()
ax.set_yticks(np.arange(0.5, len(df.index), 1))
ax.set_xticks(np.arange(0.5, len(df.columns), 1), )
ax.set_xticklabels(df.columns, rotation=10)
ax.set_yticklabels(df.index,fontsize=10)
bx.set_yticks(np.arange(0.5, len(df.index), 1))
bx.set_yticklabels(["V1","V2","V3"],fontsize=10)
ax.set_ylim([0,12])
bx.set_ylim([0,12])
ax.invert_yaxis()
bx.invert_yaxis()
plt.show()

Plotting grouped data in same plot using Pandas

In Pandas, I am doing:
bp = p_df.groupby('class').plot(kind='kde')
p_df is a dataframe object.
However, this is producing two plots, one for each class.
How do I force one plot with both classes in the same plot?
Version 1:
You can create your axis, and then use the ax keyword of DataFrameGroupBy.plot to add everything to these axes:
import matplotlib.pyplot as plt
p_df = pd.DataFrame({"class": [1,1,2,2,1], "a": [2,3,2,3,2]})
fig, ax = plt.subplots(figsize=(8,6))
bp = p_df.groupby('class').plot(kind='kde', ax=ax)
This is the result:
Unfortunately, the labeling of the legend does not make too much sense here.
Version 2:
Another way would be to loop through the groups and plot the curves manually:
classes = ["class 1"] * 5 + ["class 2"] * 5
vals = [1,3,5,1,3] + [2,6,7,5,2]
p_df = pd.DataFrame({"class": classes, "vals": vals})
fig, ax = plt.subplots(figsize=(8,6))
for label, df in p_df.groupby('class'):
df.vals.plot(kind="kde", ax=ax, label=label)
plt.legend()
This way you can easily control the legend. This is the result:
import matplotlib.pyplot as plt
p_df.groupby('class').plot(kind='kde', ax=plt.gca())
Another approach would be using seaborn module. This would plot the two density estimates on the same axes without specifying a variable to hold the axes as follows (using some data frame setup from the other answer):
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
# data to create an example data frame
classes = ["c1"] * 5 + ["c2"] * 5
vals = [1,3,5,1,3] + [2,6,7,5,2]
# the data frame
df = pd.DataFrame({"cls": classes, "indices":idx, "vals": vals})
# this is to plot the kde
sns.kdeplot(df.vals[df.cls == "c1"],label='c1');
sns.kdeplot(df.vals[df.cls == "c2"],label='c2');
# beautifying the labels
plt.xlabel('value')
plt.ylabel('density')
plt.show()
This results in the following image.
There are two easy methods to plot each group in the same plot.
When using pandas.DataFrame.groupby, the column to be plotted, (e.g. the aggregation column) should be specified.
Use seaborn.kdeplot or seaborn.displot and specify the hue parameter
Using pandas v1.2.4, matplotlib 3.4.2, seaborn 0.11.1
The OP is specific to plotting the kde, but the steps are the same for many plot types (e.g. kind='line', sns.lineplot, etc.).
Imports and Sample Data
For the sample data, the groups are in the 'kind' column, and the kde of 'duration' will be plotted, ignoring 'waiting'.
import pandas as pd
import seaborn as sns
df = sns.load_dataset('geyser')
# display(df.head())
duration waiting kind
0 3.600 79 long
1 1.800 54 short
2 3.333 74 long
3 2.283 62 short
4 4.533 85 long
Plot with pandas.DataFrame.plot
Reshape the data using .groupby or .pivot
.groupby
Specify the aggregation column, ['duration'], and kind='kde'.
ax = df.groupby('kind')['duration'].plot(kind='kde', legend=True)
.pivot
ax = df.pivot(columns='kind', values='duration').plot(kind='kde')
Plot with seaborn.kdeplot
Specify hue='kind'
ax = sns.kdeplot(data=df, x='duration', hue='kind')
Plot with seaborn.displot
Specify hue='kind' and kind='kde'
fig = sns.displot(data=df, kind='kde', x='duration', hue='kind')
Plot
Maybe you can try this:
fig, ax = plt.subplots(figsize=(10,8))
classes = list(df.class.unique())
for c in classes:
df2 = data.loc[data['class'] == c]
df2.vals.plot(kind="kde", ax=ax, label=c)
plt.legend()

Categories