Related
data = {'name': ['A', 'B', 'C', 'D'],
'score': [-9.5, -8.3, -8.1, -7.0],
'color': [4, 3, 2, 1]}
df = pd.DataFrame(data)
I have my data in a dataframe like above and I am plotting it to a seaborn swarmplot like the one below. The points are plotted based on their score, and depending on how that falls between the 3 dotted lines, I want to color the points differently. I use the 'color' column of the df to assign a key based on where the 'score' values fall that corresponds to colors and a dictionary.
colors = {1:'pink', 2:'orange', 3:'red', 4:'green'}
I then create the swarmplot with the below code and map the color dictionary to the colors column of my df.
ax = sns.swarmplot(data=df, y='score', s=10, c=df['color'].map(colors))
When I do this I don't generate any errors, but no colors are applied, and the points remain their default blue (image below, left). So, how can I assign colors to points in a seaborn swarmplot based on my df['color'] column?
Final note: When I try to use palette=df['color'].map(colors) instead of c=df['color'].map(colors), the graph just changes everything to the last color in my colors dictionary (image below, right)
Edit: Thank you for your suggestion #Trenton McKinney. It is somewhat successful in that the colors do properly map, but when I include the 'name' column (Its actually Title) for x, as below, my points are plotted like a scatter instead of a swarm plot. But I get an error if I try to remove x='Title' from my parameters.
ax = sns.swarmplot(data=data, x='Title', y='score', s=10, hue='color', palette=colors)
As per seaborn issue 941, this is the expected behavior.
Seems like the API doesn't play well when specifying only x or only y.
The issue is resolved by passing a list of the same strings based on the length of the dataframe: ['']*len(df) or ['text']*len(df)
ax = sns.swarmplot(data=df, x=['']*len(df), y='score', hue='color', palette=colors)
I am currently implementing a code for facetgrid with subplots of barplots with two different groups ('type'), respectively. I am intending to get a plot, where the different groups are not stacked and not overlapping. I am using following code
g = sns.FacetGrid(data,
col='C',
hue = 'type',
sharex=False,
sharey=False,
size=7,
palette=sns.color_palette(['red','green']),
)
g = g.map(sns.barplot, 'A', 'B').add_legend()
The data is a pandas long format df with following example structure:
data=pd.DataFrame({'A':['X','X','Y','Y','X','X','Y','Y'],
'B':[0,1,2,3,4,5,6,7],
'C':[1,1,1,1,2,2,2,2],
'type':['ctrl','cond1','ctrl','cond1','ctrl','cond1','ctrl','cond1']}
)
In the created barplots I get now fully overlapping barplots of the two groups, thus ctrlis missing, see below. However, I am intending to get neighbouring non-overlapping bars each. How to achieve that? My real code has some more bars per plot, where you can see overlapping colors (here fully covered)
this answer shows up how to use FacetGrid directly.
But, if you have 0.9.0 installed, I would recommend you make use of the new catplot() function that will produce the right (at least I think?) plot. Note that this function returns a FacetGrid object. You can pass kwargs to the call to customize the resulting FacetGrid, or modify its properties afterwards.
g = sns.catplot(data=data, x='A', y='B', hue='type', col='C', kind='bar')
I think you want to provide the hue argument to the barplot, not the FacetGrid. Because the grouping takes place within the (single) barplot, not on the facet's level.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
data=pd.DataFrame({'A':['X','X','Y','Y','X','X','Y','Y'],
'B':[0,1,2,3,4,5,6,7],
'C':[1,1,1,1,2,2,2,2],
'type':['ctrl','cond1','ctrl','cond1','ctrl','cond1','ctrl','cond1']})
g = sns.FacetGrid(data,
col='C',
sharex=False,
sharey=False,
height=4)
g = g.map(sns.barplot, 'A', 'B', "type",
hue_order=np.unique(data["type"]),
order=["X", "Y"],
palette=sns.color_palette(['red','green']))
g.add_legend()
plt.show()
I would like to "maximize" the color space for a plot in seaborn. By that I mean I would like the color range to include the two extreme colors in a given palette.
For example if I choose the matplotlib color palette "YlGn_r" and plot it with pandas:
%matplotlib inline
import pandas as pd
import numpy as np
import seaborn as sns
df = pd.DataFrame(np.random.rand(10, 4), columns=['a', 'b', 'c', 'd'])
df.plot(kind='bar', colormap='YlGn_r', width=.8);
If I plot this with the same palette but seaborn the colors are different:
df['x'] = df.index
sns.barplot(x='x', y='value', hue='variable', data=pd.melt(df, 'x'), palette='YlGn_r')
I realize this is probably intended behavior, which I am not entirely opposed to, however is there a way to force seaborn to use the full spectrum? I have many plots that need the colors to match, some in seaborn and some with matplotlib. Thanks!
You can just pass a list of colors anywhere a palette name is accepted, so if you want specific colors, that's the best way to get them. One way would be mpl.cm.YlGn_r(np.linspace(0, 1, 4)).
However, barplot also desaturate the colors a bit, which looks better with large patches, but if you don't want that you can set saturation=1.
I am trying to create a single image with heatmaps representing the correlation of features of data points for each label separately. With seaborn I can create a heatmap for a single class like so
grouped = df.groupby('target')
sns.heatmap(grouped.get_group('Class_1').corr())
An I get this which makes sense:
But then I try to make a list of all the labels like so:
g = sns.FacetGrid(df, col='target')
g.map(lambda grp: sns.heatmap(grp.corr()))
And sadly I get this which makes no sense to me:
Turns out you can do it pretty concisely with just seaborn if you use map_dataframe instead of map:
g = sns.FacetGrid(df, col='target')
g.map_dataframe(lambda data, color: sns.heatmap(data.corr(), linewidths=0))
#mwaskom points out in his comment that it might be a good idea to explicitly set the limits of the colormap so that the different facets can be more directly compared. The documentation describes relevant heatmap parameters:
vmin, vmax : floats, optional
Values to anchor the colormap, otherwise they are inferred from the data and other keyword arguments.
Without FacetGrid, but making a corr heatmap for each group in a column:
import pandas as pd
import seaborn as sns
from numpy.random import randint
import matplotlib.pyplot as plt
df = pd.DataFrame(randint(0,10,(200,12)),columns=list('abcdefghijkl'))
grouped = df.groupby('a')
rowlength = grouped.ngroups/2 # fix up if odd number of groups
fig, axs = plt.subplots(figsize=(9,4), nrows=2, ncols=rowlength)
targets = zip(grouped.groups.keys(), axs.flatten())
for i, (key, ax) in enumerate(targets):
sns.heatmap(grouped.get_group(key).corr(), ax=ax,
xticklabels=(i >= rowlength),
yticklabels=(i%rowlength==0),
cbar=False) # Use cbar_ax into single side axis
ax.set_title('a=%d'%key)
plt.show()
Maybe there's a way to set up a lambda to correctly pass the data from the g.facet_data() generator through corr before going to heatmap.
I have this data frame diamonds which is composed of variables like (carat, price, color), and I want to draw a scatter plot of price to carat for each color, which means different color has different color in the plot.
This is easy in R with ggplot:
ggplot(aes(x=carat, y=price, color=color), #by setting color=color, ggplot automatically draw in different colors
data=diamonds) + geom_point(stat='summary', fun.y=median)
I wonder how could this be done in Python using matplotlib ?
PS:
I know about auxiliary plotting packages, such as seaborn and ggplot for python, and I don't prefer them, just want to find out if it is possible to do the job using matplotlib alone, ;P
Imports and Sample DataFrame
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns # for sample data
from matplotlib.lines import Line2D # for legend handle
# DataFrame used for all options
df = sns.load_dataset('diamonds')
carat cut color clarity depth table price x y z
0 0.23 Ideal E SI2 61.5 55.0 326 3.95 3.98 2.43
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31
2 0.23 Good E VS1 56.9 65.0 327 4.05 4.07 2.31
With matplotlib
You can pass plt.scatter a c argument, which allows you to select the colors. The following code defines a colors dictionary to map the diamond colors to the plotting colors.
fig, ax = plt.subplots(figsize=(6, 6))
colors = {'D':'tab:blue', 'E':'tab:orange', 'F':'tab:green', 'G':'tab:red', 'H':'tab:purple', 'I':'tab:brown', 'J':'tab:pink'}
ax.scatter(df['carat'], df['price'], c=df['color'].map(colors))
# add a legend
handles = [Line2D([0], [0], marker='o', color='w', markerfacecolor=v, label=k, markersize=8) for k, v in colors.items()]
ax.legend(title='color', handles=handles, bbox_to_anchor=(1.05, 1), loc='upper left')
plt.show()
df['color'].map(colors) effectively maps the colors from "diamond" to "plotting".
(Forgive me for not putting another example image up, I think 2 is enough :P)
With seaborn
You can use seaborn which is a wrapper around matplotlib that makes it look prettier by default (rather opinion-based, I know :P) but also adds some plotting functions.
For this you could use seaborn.lmplot with fit_reg=False (which prevents it from automatically doing some regression).
sns.scatterplot(x='carat', y='price', data=df, hue='color', ec=None) also does the same thing.
Selecting hue='color' tells seaborn to split and plot the data based on the unique values in the 'color' column.
sns.lmplot(x='carat', y='price', data=df, hue='color', fit_reg=False)
With pandas.DataFrame.groupby & pandas.DataFrame.plot
If you don't want to use seaborn, use pandas.groupby to get the colors alone, and then plot them using just matplotlib, but you'll have to manually assign colors as you go, I've added an example below:
fig, ax = plt.subplots(figsize=(6, 6))
grouped = df.groupby('color')
for key, group in grouped:
group.plot(ax=ax, kind='scatter', x='carat', y='price', label=key, color=colors[key])
plt.show()
This code assumes the same DataFrame as above, and then groups it based on color. It then iterates over these groups, plotting for each one. To select a color, I've created a colors dictionary, which can map the diamond color (for instance D) to a real color (for instance tab:blue).
Here's a succinct and generic solution to use a seaborn color palette.
First find a color palette you like and optionally visualize it:
sns.palplot(sns.color_palette("Set2", 8))
Then you can use it with matplotlib doing this:
# Unique category labels: 'D', 'F', 'G', ...
color_labels = df['color'].unique()
# List of RGB triplets
rgb_values = sns.color_palette("Set2", 8)
# Map label to RGB
color_map = dict(zip(color_labels, rgb_values))
# Finally use the mapped values
plt.scatter(df['carat'], df['price'], c=df['color'].map(color_map))
I had the same question, and have spent all day trying out different packages.
I had originally used matlibplot: and was not happy with either mapping categories to predefined colors; or grouping/aggregating then iterating through the groups (and still having to map colors). I just felt it was poor package implementation.
Seaborn wouldn't work on my case, and Altair ONLY works inside of a Jupyter Notebook.
The best solution for me was PlotNine, which "is an implementation of a grammar of graphics in Python, and based on ggplot2".
Below is the plotnine code to replicate your R example in Python:
from plotnine import *
from plotnine.data import diamonds
g = ggplot(diamonds, aes(x='carat', y='price', color='color')) + geom_point(stat='summary')
print(g)
So clean and simple :)
Here a combination of markers and colors from a qualitative colormap in matplotlib:
import itertools
import numpy as np
from matplotlib import markers
import matplotlib.pyplot as plt
m_styles = markers.MarkerStyle.markers
N = 60
colormap = plt.cm.Dark2.colors # Qualitative colormap
for i, (marker, color) in zip(range(N), itertools.product(m_styles, colormap)):
plt.scatter(*np.random.random(2), color=color, marker=marker, label=i)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0., ncol=4);
Using Altair.
from altair import *
import pandas as pd
df = datasets.load_dataset('iris')
Chart(df).mark_point().encode(x='petalLength',y='sepalLength', color='species')
The easiest way is to simply pass an array of integer category levels to the plt.scatter() color parameter.
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/diamonds.csv')
plt.scatter(df['carat'], df['price'], c=pd.factorize(df['color'])[0],)
plt.gca().set(xlabel='Carat', ylabel='Price', title='Carat vs. Price')
This creates a plot without a legend, using the default "viridis" colormap. In this case "viridis" is not a good default choice because the colors appear to imply a sequential order rather than purely nominal categories.
To choose your own colormap and add a legend, the simplest approach is this:
import matplotlib.patches
levels, categories = pd.factorize(df['color'])
colors = [plt.cm.tab10(i) for i in levels] # using the "tab10" colormap
handles = [matplotlib.patches.Patch(color=plt.cm.tab10(i), label=c) for i, c in enumerate(categories)]
plt.scatter(df['carat'], df['price'], c=colors)
plt.gca().set(xlabel='Carat', ylabel='Price', title='Carat vs. Price')
plt.legend(handles=handles, title='Color')
I chose the "tab10" discrete (aka qualitative) colormap here, which does a better job at signaling the color factor is a nominal categorical variable.
Extra credit:
In the first plot, the default colors are chosen by passing min-max scaled values from the array of category level ints pd.factorize(iris['species'])[0] to the call method of the plt.cm.viridis colormap object.
With df.plot()
Normally when quickly plotting a DataFrame, I use pd.DataFrame.plot(). This takes the index as the x value, the value as the y value and plots each column separately with a different color.
A DataFrame in this form can be achieved by using set_index and unstack.
import matplotlib.pyplot as plt
import pandas as pd
carat = [5, 10, 20, 30, 5, 10, 20, 30, 5, 10, 20, 30]
price = [100, 100, 200, 200, 300, 300, 400, 400, 500, 500, 600, 600]
color =['D', 'D', 'D', 'E', 'E', 'E', 'F', 'F', 'F', 'G', 'G', 'G',]
df = pd.DataFrame(dict(carat=carat, price=price, color=color))
df.set_index(['color', 'carat']).unstack('color')['price'].plot(style='o')
plt.ylabel('price')
With this method you do not have to manually specify the colors.
This procedure may make more sense for other data series. In my case I have timeseries data, so the MultiIndex consists of datetime and categories. It is also possible to use this approach for more than one column to color by, but the legend is getting a mess.
You can convert the categorical column into a numerical one by using the commands:
#we converting it into categorical data
cat_col = df['column_name'].astype('category')
#we are getting codes for it
cat_col = cat_col.cat.codes
# we are using c parameter to change the color.
plt.scatter(df['column1'],df['column2'], c=cat_col)