2

I have a multi-plot bar plot figure produced with matplotlib/ seaborn and I'd like to control the tick lines and background style. When I try to use sns.set_style("whitegrid"), the background is grey/ not whitegrid.

Example data, similar structure to mine:

data = {
  "Country": ["UK", "UK", "UK", "UK", "UK", "France", "France", "France", "France", "France"],
  "Variable": ["A", "B", "C", "D", "E", "A", "B", "C", "D", "E"],
  "Counts": [5, 10, 7, 9, 12, 25, 29, 21, 23, 31]
}
df = pd.DataFrame(data)
df

And here is the plotting code:

fig, ax = plt.subplots(2,1, figsize=(7,7))

### Set seaborn theme
sns.set_style("whitegrid")
sns.set(font_scale=1.2)
fontsize = 15
fontsize_labels1 = 12
fontsize_labels2 = 11

### Colour palette for pfsa variants
col_uk  = '#dd5129'
col_france  = '#0f7ba2'

### Define limits
ylim = 40 
country_text_y = 35

### plot 1 - UK
plot_counts_uk = df.loc[df['Country'] == "UK"]

p_uk = sns.barplot(data=plot_counts_uk, x="Variable", y="Counts",
                      color=col_uk, ax=ax[0])
p_uk.set_ylim(0, ylim)
ax[0].set(ylabel='Count', xlabel=None)
x_axis1 = p_uk.axes.get_xaxis()
x_axis1.set_visible(False)
p_uk.text(0, country_text_y, "UK", horizontalalignment='center', fontsize=fontsize_labels1, color='black')


### plot 2 - France
plot_counts_france = df.loc[df['Country'] == "France"]

variables_list = plot_counts_france.Variable.values.tolist()

p_france = sns.barplot(data=plot_counts_france, x="Variable", y="Counts",
                      color=col_france, ax=ax[1])
ax[1].set_xticklabels(variables_list, rotation=40, ha='right')
p_france.set_ylim(0, ylim)
ax[1].set(ylabel='Count', xlabel=None)
x_axis2 = p_france.axes.get_xaxis()
x_axis2.set_visible(True)
p_france.text(0, country_text_y, "France", horizontalalignment='center', fontsize=fontsize_labels1, color='black')

fig.tight_layout()

Despite sns.set_style("whitegrid"), the plots all have a grey background. I have tried adding facecolor='#FFFFFF' but there is no change:

fig, ax = plt.subplots(2,1, figsize=(7,7), facecolor='#FFFFFF')

Output:

Code output - grey background

Changing the sns.set_style() argument to other things like sns.set_style("dark") also doesn't change anything.

Ideally I'd also like to control the tick markings on the plots, though that depends what it looks like if it actually had a whitegrid style. What I have in mind is vertical minor lines in the centre of each bar to keep track of the x-axis labels (A, B, C, D, E), and to set the y-axis ticks to something sensible that would apply to all the plots. A lot of that may be accomplished by the whitegrid style but I can't see what that looks like.

Thanks for the help!

1 Answer 1

0

I created a subplot with sns and matplotlib that I believe is closer to what you have in mind. It includes use of subplot titles, gridlines, customized text, and updated ticks. My figure and full code are at the top and more detailed explanations of changes below. NOTE: I used the Iris dataset for convenience to show the process, so be sure to update for your real data. Also, the figure is just to show the formatting, it is not meaningful.

Full Figure

Iris Figure with Subplots

Full Code

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Only necessary in notebooks
%matplotlib inline 

df = sns.load_dataset('iris')

fig, axes = plt.subplots(2,1, figsize=(7,7)) # change ax to axes since multiple plots

fontsize = 15
fontsize_labels1 = 12
fontsize_labels2 = 11

### Colour palette for pfsa variants
col_setosa  = '#dd5129'
col_versicolor = '#0f7ba2'

### Define limits
ylim = 40 
country_text_y = 35

# dfs for plotting
### Subsets df, creates value counts and reformats into a new df
### New df has 2 cols: ['sepal_length', 'count']

# Setosa 
plot_counts_setosa = df[df['species'] == "setosa"]['sepal_length']
setosa_counts = plot_counts_setosa.value_counts().to_frame().reset_index()
setosa_counts.columns

# Versicolor
plot_counts_versicolor = df[df['species'] == "versicolor"]['sepal_length']
versicolor_counts = plot_counts_versicolor.value_counts().to_frame().reset_index()
versicolor_counts.columns

# Subplots

# Setosa
p_setosa = sns.barplot(data=setosa_counts, x="sepal_length", y="count",
                      color=col_setosa, ax=axes[0])
p_setosa.set_ylim(0, ylim) #sets y limit
axes[0].set(ylabel='Count', xlabel=None) # Removes x axis label
x_axis1 = p_setosa.axes.get_xaxis() 
x_axis1.set_visible(False) # Removes x axis ticks
p_setosa.set_title('Setosa') # Sets a title on the subplot

# Versicolor
p_versicolor = sns.barplot(data=versicolor_counts, x="sepal_length", y="count",
                      color=col_versicolor, ax=axes[1])
p_versicolor.set_ylim(0, ylim) # Sets y limit
axes[1].set(ylabel='Count', xlabel=None) # Removes x axis label
p_versicolor.set_title("Versicolor") # Set subplot title

# Turn on grids for each subplot
for ax in axes.flat:
    ax.grid(True)

# See tick value and labels for x and y for all subplots
for ax in axes.flat:
    x_labels = [label.get_text() for label in ax.get_xticklabels()]
    print("X labels:", x_labels)

    x_ticks = ax.get_xticks()
    print("X ticks:", x_ticks)

    y_labels = [label.get_text() for label in ax.get_yticklabels()]
    print("Y labels:", y_labels)

    y_ticks = ax.get_yticks()
    print("Y ticks:", y_ticks)

# Set ticks, labels, and position of x-axis on subplot 2
# Moved to top here for easier comparison with 2 stacked plots
axes[1].set_xticks([1, 5, 10, 14])
axes[1].set_xticklabels(['Lowest', 'Medium', 'High', 'Extreme'], fontsize=12, 
                   fontweight="bold", color="darkblue", rotation=40)
# Move x-axis ticks to the top
axes[1].tick_params(top=True, labeltop=True, bottom=False, labelbottom=False)

# Format y-axis for all subplots
for ax in axes:
    ax.set_yticks([0, 5, 10, 15, 20, 25, 30, 35, 40]) # Set which to use+
    ax.set_ylabel("Custom Y Label", fontsize=13, fontstyle="italic") # Add custom label

# Add figure title
fig.suptitle("Iris Plot", fontsize=16)
# Set tight_layout
fig.tight_layout()
# Save figure
plt.savefig("IrisPlot.png")

Background Colors for Figure and Plots

fig, ax = plt.subplots(2,1, figsize=(7,7), facecolor='blue') adds a background color to the figure.

The below adds a background color to each of the subplots

ax[0].set_facecolor(col_setosa)
ax[1].set_facecolor(col_versicolor)

Add Grids

You can manually control grid lines for each plot. A few examples show below. From my testing, it appears grid lines may be linked to ticks, so extra steps may be necessary to add them on an axis without ticks.

# Turn on grid for all subplots
for ax in axes.flat:
    ax.grid(True)

# Turn on x axis grid for first subplot
axes[0].grid(True, axis="x")

Figure and Subplot Titles

I think the country titles in the original were meant as titles for each plot. Using the built-in title options for this is cleaner and easier to format. Subplot titles here will have all the functionality of Matplotlib titles.

fig.suptitle("Iris Plot", fontsize=16) # Figure title
p_setosa.set_title('Setosa') # Suplot 1 title
p_versicolor.set_title("Versicolor") # Subplot 2 title

# Move title to left
p_versicolor.set_title("Versicolor", loc='left')

Ticks

Ticks can be highly customized, the key to working with them is understanding which axis is being targeted and that each tick has multiple components. See the docs for more details. Here I want to focus on getting existing tick info, and updating them. Changing size, colors, and more is possible, but not covered here.

Getting Tick Details

Labels hold more info than just what is displayed. The below examples creates a list of just the text of the labels. The tick values are where each of the labels is placed along the axis.

for ax in axes.flat:
    x_labels = [label.get_text() for label in ax.get_xticklabels()]
    print("X labels:", x_labels)

    x_ticks = ax.get_xticks()
    print("X ticks:", x_ticks)

Example output: X labels: ['4.3', '4.4', '4.5', '4.6', '4.7', '4.8', '4.9', '5.0', '5.1', '5.2', '5.3', '5.4', '5.5', '5.7', '5.8'] X ticks: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] X labels: ['4.9', '5.0', '5.1', '5.2', '5.4', '5.5', '5.6', '5.7', '5.8', '5.9', '6.0', '6.1', '6.2', '6.3', '6.4', '6.5', '6.6', '6.7', '6.8', '6.9', '7.0'] X ticks: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

Setting Tick Details

To add your own tick details, set the ticks, then the labels, and use tick_params for higher level manipulation like change the location of the axis ticks. NOTE: text formatting can be passed at the set_xticklabels level.

axes[1].set_xticks([1, 5, 10, 14])
axes[1].set_xticklabels(['Lowest', 'Medium', 'High', 'Extreme'], fontsize=12, 
                   fontweight="bold", color="darkblue", rotation=40)
# Move x-axis ticks to the top
axes[1].tick_params(top=True, labeltop=True, bottom=False, labelbottom=False)

Happy Plotting

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.