Full API

betterplotlib.subplots(*args, **kwargs)[source]

A wrapper to the plt.subplots() function, and is the main way to access the betterplotlib functionality.

This is exactly the same as the plt.subplots() function, only that it creates betterplotlib axes objects rather than matplotlib ones. The betterplotlib objects are where all the magic happens.

betterplotlib.set_style(style='default', font='Lato', fontweight='semibold')[source]

Set style settings that will apply to all plots after this function call.

Parameters:
  • style (str) – I have three predefined styles: “default”, “white””, and “latex”. On the whole, they’re all pretty similar. The default style is appropriate for presentations and paper figures. “white” takes the default style and turns the axes white, so that it can be used in a presentation with a dark slide background. When using the “white” style, you’ll probably want to save the figure with the “transparent=True” keyword so that the slide background shows through. Finally, the “latex” style uses LaTeX to render all text. Note that all styles can fully render LaTeX equations in axis labels and other text, but the “latex”theme uses the Computer Modern LaTeX font.

  • font (str) – For the default and white styles, the font to use. Must be the name of a font available through Google fonts. The font will be automatically downloaded if not currently present on your system.

  • fontweight (str) – The weight of the font

Returns:

None

import betterplotlib as bpl
bpl.set_style() # default

fig, ax = bpl.subplots()
for i in range(3):
    ax.scatter(np.random.normal(i, 1, 100), np.random.normal(i, 1, 100))
ax.add_labels("X Label", "Y Label", "Title")
_images/full_api-1.png
import betterplotlib as bpl
bpl.set_style(font="DejaVu Sans")

fig, ax = bpl.subplots()
for i in range(3):
    ax.scatter(np.random.normal(i, 1, 100), np.random.normal(i, 1, 100))
ax.add_labels("X Label", "Y Label", "Title")
_images/full_api-2.png
import betterplotlib as bpl
bpl.set_style("white")

fig, ax = bpl.subplots()
for i in range(3):
    ax.scatter(np.random.normal(i, 1, 100), np.random.normal(i, 1, 100))
ax.add_labels("X Label", "Y Label", "Title")
_images/full_api-3.png

If you download this previous image you can see that the background is transparent and the axes are white.

import betterplotlib as bpl
bpl.set_style("latex")

fig, ax = bpl.subplots()
for i in range(3):
    ax.scatter(np.random.normal(i, 1, 100), np.random.normal(i, 1, 100))
ax.add_labels("X Label", "Y Label", "Title")
_images/full_api-4.png
betterplotlib.create_mappable(vmin, vmax, cmap, log=False)[source]

Create a ScalarMappable, which is used to get colors from a colorbar

This is also often used to seed a colorbar.

Parameters:
  • vmin (float) – Value corresponding to the minimum value of the colormap

  • vmax (float) – Value corresponding to the maximum value of the colormap

  • cmap – The colormap to use. Can be a string with the colormap name or a colormap object

  • log (bool) – Whether or not to use log scaling on the normalization between vmin and vmax

Returns:

The ScalarMappable object

Return type:

matplotlib.cm.ScalarMappable

import betterplotlib as bpl
import numpy as np

bpl.set_style()

mappable = bpl.create_mappable(0, 1, "Blues")
fig, ax = bpl.subplots()
for i in np.arange(0, 1.01, 0.1):
    ax.plot([0, 1], [i, i], c=mappable.to_rgba(i))
ax.set_limits(0, 1, -0.05, 1.05)
fig.colorbar(mappable, ax=ax)
_images/full_api-5.png
import betterplotlib as bpl
import numpy as np

bpl.set_style()

mappable = bpl.create_mappable(1, 100, "Blues", log=True)
fig, ax = bpl.subplots()
for i in np.logspace(0, 2, 10):
    ax.plot([0, 1], [i, i], c=mappable.to_rgba(i))
ax.log("y")
ax.set_limits(0, 1, 0.9, 110)
fig.colorbar(mappable, ax=ax)
_images/full_api-6.png
betterplotlib.fade_color(color)[source]

Create a faded version of a different color

Parameters:

color – the original color. Can be in any format

Returns:

the hex color of the faded version

Return type:

str

import betterplotlib as bpl
import numpy as np
bpl.set_style()

c = "#ac4649"
fig, ax = bpl.subplots()

ax.axhline(2, lw=20, c=c)
ax.axhline(1, lw=20, c=bpl.fade_color(c))
ax.axhline(0, lw=20, c=bpl.unfade_color(bpl.fade_color(c)))

ax.add_text(0.5, 2.1, "Original", va="bottom", ha="center")
ax.add_text(0.5, 1.1, "Faded", va="bottom", ha="center")
ax.add_text(0.5, 0.1, "Faded then Unfaded", va="bottom", ha="center")

ax.set_limits(0, 1, -1, 3)
ax.remove_labels("both")
_images/full_api-7.png
betterplotlib.unfade_color(color)[source]

Undo the fading applied by fade_color

Parameters:

color – the faded color. Can be in any format. This color must have a low enough saturation to be unfaded. A ValueError will be raised if this color is too saturated to be unfaded.

Returns:

the hex color of the unfaded version

Return type:

str

import betterplotlib as bpl
import numpy as np
bpl.set_style()

c = "#ac4649"
fig, ax = bpl.subplots()

ax.axhline(2, lw=20, c=c)
ax.axhline(1, lw=20, c=bpl.fade_color(c))
ax.axhline(0, lw=20, c=bpl.unfade_color(bpl.fade_color(c)))

ax.add_text(0.5, 2.1, "Original", va="bottom", ha="center")
ax.add_text(0.5, 1.1, "Faded", va="bottom", ha="center")
ax.add_text(0.5, 0.1, "Faded then Unfaded", va="bottom", ha="center")

ax.set_limits(0, 1, -1, 3)
ax.remove_labels("both")
_images/full_api-8.png
class betterplotlib.Axes_bpl(*args, **kwargs)[source]
add_labels(x_label=None, y_label=None, title=None, *args, **kwargs)[source]

Adds labels to the x and y axis, plus a title.

Addition properties will be passed the all single label creations, so any properties will be applied to all. If you want the title to be different, for example, don’t include it here.

Parameters:
  • x_label (str) – label for the x axis

  • y_label (str) – label for the y axis

  • title (str) – title for the given axis

  • args – additional properties that will be passed on to all the labels you asked for.

  • kwargs – additional keyword arguments that will be passed on to all the labels you make.

Returns:

None

Example:

import betterplotlib as bpl
import numpy as np
bpl.set_style()

xs = np.arange(0, 10, 0.1)
ys = xs**2

fig, ax = bpl.subplots()
ax.plot(xs, ys)
ax.add_labels("X value", "Y value", "Title")
_images/full_api-9.png
add_text(x, y, text, coords='data', border_color=None, border_linewidth=3, **kwargs)[source]

Adds text to the specified location. Allows for easy specification of the type of coordinates you are specifying.

Matplotlib allows the text to be in data or axes coordinates, but it’s hard to remember the command for that. This fixes that. The param coords takes care of that.

The x and y locations can be specified in either data or axes coords. If data coords are used, the text is placed at that data point. If axes coords are used, the text is placed relative to the axes. (0,0) is the bottom left, (1,1) is the top right. Remember to use the horizontalalignment and verticalalignment parameters if it isn’t quite in the spot you expect.

Also consider using easy_add_text, which gives 9 possible location to add text with minimal consternation.

Parameters:
  • x (int, float) – x location of the text to be added.

  • y (int, float) – y location of the text to be added.

  • text (str) – text to be added

  • coords – type of coordinates. This parameter can be either ‘data’

or ‘axes’. ‘data’ puts the text at that data point. ‘axes’ puts the text in that location relative the axes. See above. :type coords: str :param border_color: An optional color to add a border around the text added.

This is useful for making text more easily visible against a colorful background

Parameters:
  • border_linewidth (int) – the width of the border added around the text

  • kwargs – any additional keyword arguments to pass on the text function. Pass things you would pass to plt.text()

Returns:

Same as output of plt.text().

Example:

import betterplotlib as bpl
import numpy as np
bpl.set_style()

xs = np.random.normal(0, 1, 1000)
ys = np.random.normal(0, 1, 1000)

bpl.density_contourf(xs, ys, bin_size=0.1, smoothing=0.5)
bpl.set_limits(-4, 4, -4, 4)
bpl.equal_scale()
bpl.add_text(-3, 3, "(-3, 3) data")
bpl.add_text(0.7, 0.1, "70% across, 10% up", "axes")
bpl.add_text(
    0,
    0,
    "(0, 0) data, black border",
    color="white",
    border_color=bpl.almost_black,
    border_linewidth=3,
)
_images/full_api-10.png
axhline(y=0, *args, **kwargs)[source]

Place a horizontal line at some point on the axes.

Parameters:
  • y (float) – Data value on the y-axis to place the line.

  • args – Additional parameters that will be passed on the the regular plt.axhline function. See it’s documentation for details.

  • kwargs – Similarly, additional keyword arguments that will be passed on to the regular plt.axhline function.

import numpy as np
import betterplotlib as bpl
bpl.set_style()

left_xs = np.arange(-20, 1, 0.01)
right_xs = np.arange(1.001, 20, 0.01)
left_ys = left_xs / (left_xs - 1)
right_ys = right_xs / (right_xs - 1)

bpl.make_ax_dark()
bpl.plot(left_xs, left_ys, c=bpl.color_cycle[2])
bpl.plot(right_xs, right_ys, c=bpl.color_cycle[2])
bpl.axvline(1.0, linestyle="--")
bpl.axhline(1.0, linestyle="--")
bpl.set_limits(-10, 10, -10, 10)
_images/full_api-11.png
axvline(x=0, *args, **kwargs)[source]

Place a vertical line at some point on the axes.

Parameters:
  • x (float) – Data value on the x-axis to place the line.

  • args – Additional parameters that will be passed on the the regular plt.axvline function. See it’s documentation for details.

  • kwargs – Similarly, additional keyword arguments that will be passed on to the regular plt.axvline function.

import numpy as np
import betterplotlib as bpl
bpl.set_style()

left_xs = np.arange(-20, 1, 0.01)
right_xs = np.arange(1.001, 20, 0.01)
left_ys = left_xs / (left_xs - 1)
right_ys = right_xs / (right_xs - 1)

bpl.make_ax_dark()
bpl.plot(left_xs, left_ys, c=bpl.color_cycle[2])
bpl.plot(right_xs, right_ys, c=bpl.color_cycle[2])
bpl.axvline(1.0, linestyle="--")
bpl.axhline(1.0, linestyle="--")
bpl.set_limits(-10, 10, -10, 10)
_images/full_api-12.png
contour_scatter(xs, ys, bin_size=None, percent_levels=None, smoothing=0, weights=None, labels=False, fill_cmap='white', scatter_kwargs=None, contour_kwargs=None, contourf_kwargs=None)[source]

Create a contour plot with scatter points in the sparse regions.

When a dataset is large, plotting a scatterplot is often really hard to understand, due to many points overlapping and the high density of points overall. A contour or hexbin plot solves many of these problems, but these still have the disadvantage of making outliers less obvious. A simple solution is to plot contours in the dense regions, while plotting individual points where the density is low. That is what this function does.

Here’s how this works under the hood. Skip this paragraph if you don’t care; it won’t affect how you use this. This function uses the numpy 2D histogram function to create an array representing the density in each region. If no binning info is specified by the user, the Freedman-Diaconis algorithm is used in both dimensions to find the ideal bin size for the data. First, an opaque filled contour is plotted, then the contour lines are put on top. Then the outermost contour is made into a matplotlib path object, which lets us check which of the points are outside of this contour. Only the points that are outside are plotted.

Parameters:
  • xs (list, ndarray) – list of x values

  • ys (list, ndarray) – list of y values

  • bin_size (int, float, list) – Bin size to use for the underlying 2D histogram. This can either be a scalar, in which case the bin size will be the same in both the x dimensions, or else a two element list, where the first element will be the bin size in the x dimension, and the second will be the bin size in the y dimension.

  • percent_levels (float, list) – A list describing the levels of the contours that will be drawn. Each value in this list contains a float between zero and 1 (inclusive) that describes how much of that data will be enclosed by a contour. So if you pass [0.25, 0.5, 0.75], there will be three contours drawn, that enclose 25%, 50%, and 75% of the data. If this is not passed in, the default is [0.25, 0.5, 0.75, 0.95].

  • smoothing (int, float, list) – Optional parameter that will allow the contours to be smoothed. Pass in a nonzero value, which will be the standard deviation of the Gaussian kernel use to smooth the histogram. When using this, often choosing smaller bin sizes is advantageous to make a less grainy plot. Has the same format as padding and bin_size, so different smoothing kernels are possible in the x and y directions.

  • weights (list, np.ndarray) – A list containing weights for each data point. If these are not passed, all data points will be weighted equally.

  • labels (bool) – Whether or not to label the individual contour lines with their percentage level.

  • fill_cmap (str, matplotlib.colors.LinearSegmentedColormap) – The colormap used for the filled regions. Can be a strong with any named matplotlib colormap or a colormap object. In addition, there are some special strings that can be used. “white”, which is just a solid white fill, is the default. “background_grey” gives a solid fill that is the same color as the make_ax_dark() background. “modified_greys” is a colormap that starts at the “background_grey” color, then transitions to black.

  • scatter_kwargs (dict) – Dictionary of additional parameters that will be passed to the underlying matplotlib scatter function used for points in the outer regions.

  • contour_kwargs (dict) – Dictionary of additional parameters that will be passed to the underlying matplotlib contour function.

  • contourf_kwargs (dict) – Dictionary of additional parameters that will be passed to the underlying matplotlib contourf function.

Examples

First, we’ll show why this plot is useful. This won’t use any of the fancy settings, other than bin_size, which is used to make the contours look nicer.

import numpy as np
import betterplotlib as bpl

bpl.set_style()

xs = np.concatenate(
    [
        np.random.normal(0, 1, 100000),
        np.random.normal(3, 1, 100000),
        np.random.normal(0, 1, 100000),
    ]
)
ys = np.concatenate(
    [
        np.random.normal(0, 1, 100000),
        np.random.normal(3, 1, 100000),
        np.random.normal(3, 1, 100000),
    ]
)

fig, (ax1, ax2) = bpl.subplots(ncols=2, figsize=[10, 5])

ax1.scatter(xs, ys)
ax2.contour_scatter(xs, ys, bin_size=0.3)
_images/full_api-13.png

The scatter plot is okay, but the contour makes things easier to see.

We’ll now mess with some of the other parameters. This plot shows how the bin_size parameter changes things.

import numpy as np
import betterplotlib as bpl

bpl.set_style()

xs = np.concatenate(
    [
        np.random.normal(0, 1, 10000),
        np.random.normal(3, 1, 10000),
        np.random.normal(0, 1, 10000),
    ]
)
ys = np.concatenate(
    [
        np.random.normal(0, 1, 10000),
        np.random.normal(3, 1, 10000),
        np.random.normal(3, 1, 10000),
    ]
)

fig, (ax1, ax2, ax3) = bpl.subplots(ncols=3, figsize=[15, 5])

ax1.contour_scatter(xs, ys, bin_size=0.2)
ax2.contour_scatter(xs, ys, bin_size=0.3)
ax3.contour_scatter(xs, ys, bin_size=0.5)
_images/full_api-14.png

You can see how small values of bin_size lead to more noisy contours. The code will attempt to choose its own value of bin_size if nothing is specified, but it’s normally not a very good choice.

Adjusting the smoothing is often the better way to control the noise.

import numpy as np
import betterplotlib as bpl

bpl.set_style()

xs = np.concatenate(
    [
        np.random.normal(0, 1, 10000),
        np.random.normal(3, 1, 10000),
        np.random.normal(0, 1, 10000),
    ]
)
ys = np.concatenate(
    [
        np.random.normal(0, 1, 10000),
        np.random.normal(3, 1, 10000),
        np.random.normal(3, 1, 10000),
    ]
)

fig, (ax1, ax2, ax3) = bpl.subplots(ncols=3, figsize=[15, 5])

ax1.contour_scatter(xs, ys, bin_size=0.1, smoothing=0.1)
ax2.contour_scatter(xs, ys, bin_size=0.1, smoothing=0.2)
ax3.contour_scatter(xs, ys, bin_size=0.1, smoothing=0.3)
_images/full_api-15.png

The weights behave exactly the same as they do in the other density contour functions. Here we just have 4 points, but with different weights. We also show the different smoothing for different axes, and the labels.

import betterplotlib as bpl

bpl.set_style()

xs = [1, 2, 3, 4]
ys = [1, 2, 3, 4]
weights = [1, 2, 3, 4]
bpl.contour_scatter(
    xs,
    ys,
    weights=weights,
    bin_size=0.01,
    smoothing=[0.8, 0.3],
    fill_cmap="Blues",
    labels=True,
    contour_kwargs={"colors": "k"},
)
bpl.equal_scale()
_images/full_api-16.png

Now we can mess with the fun stuff, which is the fill_cmap param and the kwargs that get passed to the scatter, contour, and contourf function calls. There is a lot of stuff going on here, just for demonstration purposes. Note that the code has some default parameters that it will choose if you don’t specify anything.

import numpy as np
import betterplotlib as bpl

bpl.set_style()

xs = np.concatenate(
    [
        np.random.normal(0, 1, 10000),
        np.random.normal(3, 1, 10000),
        np.random.normal(0, 1, 10000),
    ]
)
ys = np.concatenate(
    [
        np.random.normal(0, 1, 10000),
        np.random.normal(3, 1, 10000),
        np.random.normal(3, 1, 10000),
    ]
)

fig, axs = bpl.subplots(nrows=2, ncols=2)
[ax1, ax2], [ax3, ax4] = axs

percent_levels = [0.99, 0.7, 0.3]
smoothing = 0.2
bin_size = 0.1

ax1.contour_scatter(
    xs,
    ys,
    bin_size=bin_size,
    percent_levels=percent_levels,
    smoothing=smoothing,
    fill_cmap="background_grey",
    contour_kwargs={"cmap": "magma"},
    scatter_kwargs={"s": 10, "c": bpl.almost_black},
)
ax1.make_ax_dark()

# or we can choose our own `fill_cmap`
ax2.contour_scatter(
    xs,
    ys,
    bin_size=bin_size,
    smoothing=smoothing,
    fill_cmap="viridis",
    percent_levels=percent_levels,
    contour_kwargs={"linewidths": 1, "colors": "white"},
    scatter_kwargs={"s": 50, "c": bpl.color_cycle[3], "alpha": 0.3},
)

# There are also my colormaps that work with the dark axes
ax3.contour_scatter(
    xs,
    ys,
    bin_size=bin_size,
    smoothing=smoothing,
    fill_cmap="modified_greys",
    percent_levels=percent_levels,
    scatter_kwargs={"c": bpl.color_cycle[0]},
    contour_kwargs={
        "linewidths": [2, 0, 0, 0, 0, 0, 0],
        "colors": bpl.almost_black,
    },
)
ax3.make_ax_dark()

# the default `fill_cmap` is white.
new_linestyles = ["solid", "dashed", "dashed", "dashed"]
ax4.contour_scatter(
    xs,
    ys,
    bin_size=bin_size,
    smoothing=smoothing,
    percent_levels=percent_levels,
    scatter_kwargs={
        "marker": "^",
        "linewidth": 0.2,
        "c": bpl.color_cycle[1],
        "s": 20,
    },
    contour_kwargs={
        "linestyles": new_linestyles,
        "colors": bpl.almost_black,
    },
)
_images/full_api-17.png

Note that the contours will work appropriately for datasets with “holes”, as demonstrated here.

import numpy as np
import betterplotlib as bpl
bpl.set_style()

rad1 = np.random.normal(10, 0.75, 10000)
theta1 = np.random.uniform(0, 2 * np.pi, 10000)
x1 = [r * np.cos(t) for r, t in zip(rad1, theta1)]
y1 = [r * np.sin(t) for r, t in zip(rad1, theta1)]

rad2 = np.random.normal(20, 0.75, 20000)
theta2 = np.random.uniform(0, 2 * np.pi, 20000)
x2 = [r * np.cos(t) for r, t in zip(rad2, theta2)]
y2 = [r * np.sin(t) for r, t in zip(rad2, theta2)]

rad3 = np.random.normal(12, 0.75, 12000)
theta3 = np.random.uniform(0, 2 * np.pi, 12000)
x3 = [r * np.cos(t) + 10 for r, t in zip(rad3, theta3)]
y3 = [r * np.sin(t) + 10 for r, t in zip(rad3, theta3)]

x4 = np.random.uniform(-20, 20, 3500)
y4 = x4 + np.random.normal(0, 0.5, 3500)

y5 = y4 * (-1)

xs = np.concatenate([x1, x2, x3, x4, x4])
ys = np.concatenate([y1, y2, y3, y4, y5])

fig, ax = bpl.subplots()

ax.contour_scatter(xs, ys, smoothing=0.5, bin_size=0.5)
ax.equal_scale()
_images/full_api-18.png
data_ticks(x_data, y_data, extent=0.015, *args, **kwargs)[source]

Puts tiny ticks on the axis borders making the location of each point.

Parameters:
  • x_data (list) – list of values to mark on the x-axis.

  • y_data (list) – list of values to mark on the y-axis. This doesn’t have to be the same length as x-data, necessarily.

  • extent (float) – How far the ticks go up from the x-axis. The default is 0.02, meaning the ticks go 2% of the way to the top of the plot. Note that the ticks created by this function will have the same physical size on both axes. Since in general the x and y axes aren’t the same physical size, the ticks on the y-axis will be scaled to match the physical size of the x ticks. This means that in the default case, the y ticks won’t cover 2% of the axis, but again will be the same physical size as the x ticks.

  • args – Additional arguments to pass to the axvline and axhline functions, which is what is used to make each tick.

  • kwargs – Additional keyword arguments to pass to the axvline and axhline functions. color is an important one here, and it defaults to almost_black here.

Example

import numpy as np
import betterplotlib as bpl
bpl.set_style()

xs = np.random.normal(0, 1, 100)
ys = np.random.normal(0, 1, 100)

bpl.scatter(xs, ys)
bpl.data_ticks(xs, ys)
_images/full_api-19.png
density_contour(xs, ys, bin_size=None, percent_levels=None, smoothing=0, weights=None, log=False, labels=False, **kwargs)[source]

Creates contours over a 2D histogram of data density.

Here you pass in the location and weights of all data points, then this will calculate the 2D histogram with smartly chosen bin size, and put contours over the top of that histogram.

These contours are just lines, not filled regions. Check out density_contourf() for that.

Parameters:
  • xs (list, ndarray) – list of x values

  • ys (list, ndarray) – list of y values

  • bin_size (int, float, list) – Bin size to use for the underlying 2D histogram. This can either be a scalar, in which case the bin size will be the same in both the x dimensions, or else a two element list, where the first element will be the bin size in the x dimension, and the second will be the bin size in the y dimension.

  • percent_levels (float, list) – A list describing the levels of the contours that will be drawn. Each value in this list contains a float between zero and 1 (inclusive) that describes how much of that data will be enclosed by a contour. So if you pass [0.25, 0.5, 0.75], there will be three contours drawn, that enclose 25%, 50%, and 75% of the data. If this is not passed in, the default is [0.25, 0.5, 0.75, 0.95].

  • smoothing (int, float, list) – Optional parameter that will allow the contours to be smoothed. Pass in a nonzero value, which will be the standard deviation of the Gaussian kernel use to smooth the histogram. When using this, often choosing smaller bin sizes is advantageous to make a less grainy plot. Has the same format as padding and bin_size, so different smoothing kernels are possible in the x and y directions.

  • weights (list, np.ndarray) – A list containing weights for each data point. If these are not passed, all data points will be weighted equally.

  • log (bool, list) – Whether or not to do the smoothing and bin creation in log space. This should be used if the plot will be done on log-scaled axes.Can either be a single bool, in which case the x and y scales will both be log (or not), or a two element array, where the first is whether the x axis is log, and the second is y. If this is used, the bin_size and smoothing parameters will be interpreted as dex, rather than raw values.

  • labels (bool) – Whether or not to label the individual contour lines with their percentage level.

  • kwargs – Additional keyword arguments to pass on to the original matplotlib contour function.

Returns:

output of the matplotlib.contour function.

import betterplotlib as bpl
import numpy as np

bpl.set_style()

xs = np.concatenate(
    [
        np.random.normal(3, 2, 1000),
        np.random.normal(7, 2, 1000),
    ]
)
ys = np.concatenate(
    [
        np.random.normal(7, 2, 1000),
        np.random.normal(3, 2, 1000),
    ]
)

bpl.density_contour(xs, ys, bin_size=0.01, smoothing=0.5, cmap="inferno")
bpl.set_limits(0, 10, 0, 10)
bpl.equal_scale()
_images/full_api-20.png
import betterplotlib as bpl
import numpy as np

bpl.set_style()

xs = np.concatenate(
    [
        np.random.normal(3, 2, 1000),
        np.random.normal(7, 2, 1000),
    ]
)
ys = 10 ** np.concatenate(
    [
        np.random.normal(7, 2, 1000),
        np.random.normal(3, 2, 1000),
    ]
)

fig, ax = bpl.subplots()
ax.density_contour(
    xs, ys, bin_size=0.01, smoothing=0.5, log=[False, True], cmap="inferno"
)
ax.log("y")
ax.set_limits(0, 10, 1, 1e10)
bpl.equal_scale()
_images/full_api-21.png
density_contourf(xs, ys, bin_size=None, percent_levels=None, smoothing=0, weights=None, log=False, **kwargs)[source]

Creates filled contours over a 2D histogram of data density.

Here you pass in the location and weights of all data points, then this will calculate the 2D histogram with smartly chosen bin size, and put contours over the top of that histogram.

These contours are just filled regions with no lines. Check out density_contour() for that.

Parameters:
  • xs (list, ndarray) – list of x values

  • ys (list, ndarray) – list of y values

  • bin_size (int, float, list) – Bin size to use for the underlying 2D histogram. This can either be a scalar, in which case the bin size will be the same in both the x dimensions, or else a two element list, where the first element will be the bin size in the x dimension, and the second will be the bin size in the y dimension.

  • percent_levels (float, list) – A list describing the levels of the contours that will be drawn. Each value in this list contains a float between zero and 1 (inclusive) that describes how much of that data will be enclosed by a contour. So if you pass [0.25, 0.5, 0.75], there will be three contours drawn, that enclose 25%, 50%, and 75% of the data. If this is not passed in, the default is [0.25, 0.5, 0.75, 0.95].

  • smoothing (int, float, list) – Optional parameter that will allow the contours to be smoothed. Pass in a nonzero value, which will be the standard deviation of the Gaussian kernel use to smooth the histogram. When using this, often choosing smaller bin sizes is advantageous to make a less grainy plot. Has the same format as padding and bin_size, so different smoothing kernels are possible in the x and y directions.

  • weights (list, np.ndarray) – A list containing weights for each data point. If these are not passed, all data points will be weighted equally.

  • log (bool, list) – Whether or not to do the smoothing and bin creation in log space. This should be used if the plot will be done on log-scaled axes.Can either be a single bool, in which case the x and y scales will both be log (or not), or a two element array, where the first is whether the x axis is log, and the second is y. If this is used, the bin_size and smoothing parameters will be interpreted as dex, rather than raw values.

  • kwargs – Additional keyword arguments to pass on to the original matplotlib contour function.

Returns:

output of the matplotlib.contourf function.

import betterplotlib as bpl
import numpy as np

bpl.set_style()

xs = np.concatenate(
    [
        np.random.normal(3, 2, 1000),
        np.random.normal(7, 2, 1000),
    ]
)
ys = np.concatenate(
    [
        np.random.normal(7, 2, 1000),
        np.random.normal(3, 2, 1000),
    ]
)

bpl.density_contourf(xs, ys, bin_size=0.01, smoothing=0.5, cmap="inferno")
bpl.set_limits(0, 10, 0, 10)
bpl.equal_scale()
_images/full_api-22.png
import betterplotlib as bpl
import numpy as np

bpl.set_style()

xs = 10 ** np.concatenate(
    [
        np.random.normal(3, 2, 1000),
        np.random.normal(7, 2, 1000),
    ]
)
ys = np.concatenate(
    [
        np.random.normal(7, 2, 1000),
        np.random.normal(3, 2, 1000),
    ]
)

fig, ax = bpl.subplots()
ax.density_contourf(
    xs, ys, bin_size=0.01, smoothing=0.5, log=[True, False], cmap="inferno"
)
ax.log("x")
ax.set_limits(1, 1e10, 0, 10)
bpl.equal_scale()
_images/full_api-23.png
easy_add_text(text, location, **kwargs)[source]

Adds text in common spots easily.

This was inspired by the plt.legend() function and its loc parameter, which allows for easy placement of legends. This does a similar thing, but just for text.

This can take any additional keyword aguments that can be passed to add_text, other than coords.

VERY IMPORTANT NOTE: Although this works similar to plt.legend()’s loc parameter, the numbering is NOT the same. My numbering is based on the keypad. 1 is in the bottom left, 5 in the center, and 9 in the top right. You can also specify words that tell the location.

Parameters:
  • text (str) – Text to add to the axes.

  • location (str, int) – Location to add the text. This can be specified two in two possible ways. You can pass an integer, which puts the text at the location corresponding to that number’s location on a standard keyboard numpad. You can also pass a string that describe the location. ‘upper’, ‘center’, and ‘lower’ describe the vertical location, and ‘left’, ‘center’, and ‘right’ describe the horizontal location. You need to specify vertical, then horizontal, like ‘upper right’. Note that ‘center’ is the code for the center, not ‘center center’.

  • kwargs – additional text parameters that will be passed on to the plt.text() function. Note that this function controls the x and y location, as well as the horizonatl and vertical alignment, so do not pass those parameters.

Returns:

Same as output of plt.text()

Example:

There are two ways to specify the location, and we will demo both.

import betterplotlib as bpl
bpl.set_style()

bpl.easy_add_text("1", 1)
bpl.easy_add_text("2", 2)
bpl.easy_add_text("3", 3)
bpl.easy_add_text("4", 4)
bpl.easy_add_text("5", 5)
bpl.easy_add_text("6", 6)
bpl.easy_add_text("7", 7)
bpl.easy_add_text("8", 8)
bpl.easy_add_text("9", 9)
_images/full_api-24.png
import betterplotlib as bpl
bpl.set_style()

bpl.easy_add_text("upper left", "upper left")
bpl.easy_add_text("upper center", "upper center")
bpl.easy_add_text("upper right", "upper right")
bpl.easy_add_text("center left", "center left")
bpl.easy_add_text("center", "center")
bpl.easy_add_text("center right", "center right")
bpl.easy_add_text("lower left", "lower left")
bpl.easy_add_text("lower center", "lower center")
bpl.easy_add_text("lower right", "lower right")
_images/full_api-25.png
equal_scale()[source]

Makes the x and y axes have the same scale.

Useful for plotting things like ra and dec, something with the same quantity on both axes, or anytime the x and y axis have the same scale. It also works when both axes are in log (making one dex the same on both axes) or when only one axis is in log (one dex = one unit in linear space).

It’s really one one command, but it’s one I have a hard time remembering.

Note that this keeps the range the same from the plot as before, so you may want to adjust the limits to make the plot look better. It will keep the axes adjusted the same, though, no matter how you change the limits afterward.

Returns:

None

Examples:

import betterplotlib as bpl
import numpy as np

bpl.set_style()

# make a Gaussian with more spread in y direction
xs = np.random.normal(0, 1, 1000)
ys = np.random.normal(0, 2, 1000)

fig, [ax1, ax2] = bpl.subplots(figsize=[12, 5], ncols=2)

ax1.scatter(xs, ys)
ax2.scatter(xs, ys)

ax2.equal_scale()

ax1.add_labels(title="Looks symmetric")
ax2.add_labels(title="Shows true shape")
_images/full_api-26.png

Here is proof that changing the limits don’t change the scaling between the axes.

import betterplotlib as bpl
import numpy as np

bpl.set_style()

# make a Gaussian with more spread in y direction
xs = np.random.normal(0, 1, 1000)
ys = np.random.normal(0, 2, 1000)

fig, [ax1, ax2] = bpl.subplots(figsize=[12, 5], ncols=2)

ax1.scatter(xs, ys)
ax2.scatter(xs, ys)

ax1.equal_scale()
ax2.equal_scale()

ax1.set_limits(-10, 10, -4, 4)
ax2.set_limits(-5, 5, -10, 10)
_images/full_api-27.png

And here’s a demonstration of using this with log scaled axes

import betterplotlib as bpl
import numpy as np

bpl.set_style()

xs = np.random.normal(0, 1, 1000)
ys = 10 ** np.random.normal(0, 0.5, 1000)

fig, ax = bpl.subplots()

ax.scatter(xs, ys)
ax.log("y")
ax.set_limits(-3, 3, 10**-3, 10**3)
ax.equal_scale()
_images/full_api-28.png
errorbar(*args, **kwargs)[source]

Wrapper for the plt.errorbar() function.

Style changes: capsize is automatically zero, and the format is automatically a scatter plot, rather than the connected lines that are used by default otherwise. It also adds a black marker edge to distinguish the markers when there are lots of data poitns. Otherwise everything blends together.

Parameters:
  • args – Additional arguments to pass to the errorbar function

  • kwargs – Additional keyword arguments to pass to the errorbar function

import numpy as np
import betterplotlib as bpl
import matplotlib.pyplot as plt
bpl.set_style()

xs = np.random.normal(0, 1, 100)
ys = np.random.normal(0, 1, 100)
yerr = np.random.uniform(0.3, 0.8, 100)
xerr = np.random.uniform(0.3, 0.8, 100)

fig = plt.figure(figsize=[15, 7])
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122, projection="bpl")  # bpl subplot.

for ax in [ax1, ax2]:
    ax.errorbar(xs,   ys,   xerr=xerr, yerr=yerr, label="set 1")
    ax.errorbar(xs+1, ys+1, xerr=xerr, yerr=yerr, label="set 2")
    ax.legend()
ax1.set_title("matplotlib")
ax2.set_title("betterplotlib")
_images/full_api-29.png
hist(*args, **kwargs)[source]

A better histogram function. Also supports relative frequency plots, bin size, and hatching better than the default matplotlib implementation.

Everything is the same as the default matplotlib implementation, with the exception a few keyword parameters. rel_freq makes the histogram a relative frequency plot and bin_size controls the width of each bin.

Parameters:
  • args – non-keyword arguments that will be passed on to the plt.hist() function. These will typically be the list of values.

  • rel_freq (bool) – Whether or not to plot the histogram as a relative frequency histogram. Note that this plots the relative frequency of each bin compared to the whole sample. Even if your range excludes some of the data, it will still be included in the relative frequency calculation.

  • bin_size (float) – The width of the bins in the histogram. The bin boundaries will start at zero, and will be integer multiples of bin_size from there. Specify either this, or bins, but not both.

  • kwargs – additional controls that will be passed on through to the plt.hist() function.

Returns:

same output as plt.hist()

Examples:

The basic histogram should look nicer than the default histogram.

import betterplotlib as bpl
import matplotlib.pyplot as plt
import numpy as np

bpl.set_style()

data = np.random.normal(0, 2, 10000)

fig = plt.figure(figsize=[15, 7])
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122, projection="bpl")

ax1.hist(data)
ax2.hist(data)

ax1.set_title("matplotlib")
ax2.add_labels(title="betterplotlib")
_images/full_api-30.png

There are also plenty of options that make other histograms look nice too.

import betterplotlib as bpl
import numpy as np
bpl.set_style()

data1 = np.random.normal(-6, 1, size=10000)
data2 = np.random.normal(-2, 1, size=10000)
data3 = np.random.normal(2, 1, size=10000)
data4 = np.random.normal(6, 1, size=10000)
bin_size = 0.5
bpl.hist(
    data1,
    rel_freq=True,
    bin_size=bin_size,
)
bpl.hist(
    data2,
    rel_freq=True,
    bin_size=bin_size,
    histtype="step",
    linewidth=5,
)
bpl.hist(
    data3,
    rel_freq=True,
    bin_size=bin_size,
    histtype="stepfilled",
    hatch="o",
    alpha=0.8,
)
bpl.hist(
    data4,
    rel_freq=True,
    bin_size=bin_size,
    histtype="step",
    hatch="x",
    linewidth=4,
)

bpl.add_labels(y_label="Relative Frequency")
_images/full_api-31.png
kde(xs, smoothing, norm=False, log=False, **kwargs)[source]

Visualize a distribution in 1D with kernel density estimation

Parameters:
  • xs (list, ndarray) – The data to visualiza

  • smoothing (float, list, ndarray) – The smoothing to apply to each data point. If a single value is supplied, that will be applied to all data points. You can also supply a list with length equal to xs to use different smoothing for each data point.

  • norm (bool) – Whether to normalize the distribution so that integrates to 1.

  • log (bool) – Whether to do the KDE creation in log space. If this is used, the value for smoothing will be interpreted as dex. If norm is also used, the integration will be done in log space, meaning we integrate dlogx rather than dx.

  • kwargs – additional keyword arguments to pass to the plot function

import betterplotlib as bpl
import numpy as np

bpl.set_style()

data = np.random.normal(0, 1, 100)
bpl.kde(data, 0.5)
bpl.set_limits(-3, 3, 0)
_images/full_api-32.png

You can also use different smoothing for each data point. Note that the area contributed by each point is equal, so points with smaller smoothing give higher points.

import betterplotlib as bpl

bpl.set_style()

bpl.kde([1, 2, 3], [0.1, 0.2, 0.3])
bpl.set_limits(0, 4, 0)
_images/full_api-33.png

Finally, the normalization parameter allows comparison of datasets of different size. Here I also demonstrate that any additional keyword arguments get passed along to plot.

import betterplotlib as bpl
import numpy as np

bpl.set_style()

d1 = np.random.normal(0, 1, 10000)
d2 = np.random.normal(0.5, 1.5, 50)
bpl.kde(d1, 0.5, norm=True, lw=3, c=bpl.color_cycle[1], label="N=10,000")
bpl.kde(d2, 0.5, norm=True, lw=10, c=bpl.almost_black, label="N=50")
bpl.set_limits(-3, 3, 0)
bpl.legend()
_images/full_api-34.png

When normalization is used with log, the integration will be done in log space as well. Also, when log is used, smoothing will be interpreted in dex.

import betterplotlib as bpl
import numpy as np

bpl.set_style()

d1 = 10**np.random.normal(0, 1, 10000)
d2 = 10**np.random.normal(0.5, 1.5, 50)
bpl.kde(d1, 0.5, norm=True, log=True)
bpl.kde(d2, 0.5, norm=True, log=True)
bpl.log("x")
bpl.set_limits(1e-3, 1e3, 0)
_images/full_api-35.png
legend(linewidth=0, *args, **kwargs)[source]

Create a nicer looking legend.

Works by calling the ax.legend() function with the given args and kwargs. If some are not specified, they will be filled with values that make the legend look nice.

Parameters:
  • linewidth (float) – linewidth of the border of the legend. Defaults to zero.

  • args – non-keyword arguments passed on to the ax.legend() fuction.

  • kwargs – keyword arguments that will be passed on to the ax.legend() function. This will be things like loc, and title, etc.

Returns:

legend object returned by the ax.legend() function.

The default legend is a transparent background with no border, like so.

import numpy as np
import betterplotlib as bpl
import matplotlib.pyplot as plt
bpl.set_style()

x = np.arange(0, 5, 0.1)

fig = plt.figure(figsize=[15, 7])
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122, projection="bpl")  # bpl subplot.

for ax in [ax1, ax2]:
    ax.plot(x, x, label="x")
    ax.plot(x, 2*x, label="2x")
    ax.plot(x, 3*x, label="3x")
    ax.legend(loc=2)

ax1.set_title("matplotlib")
ax2.set_title("betterplotlib")
_images/full_api-36.png

You can still pass in any kwargs to the legend function you want.

import betterplotlib as bpl
import numpy as np

bpl.set_style()

x = np.arange(0, 5, 0.1)

bpl.plot(x, x, label="x")
bpl.plot(x, 2*x, label="2x")
bpl.plot(x, 3*x, label="3x")
bpl.legend(fontsize=20, loc=6, title="Title")
_images/full_api-37.png
log(axes, nice_format=True)[source]

Set the x and/or y axis to be log-scaled

Parameters:
  • axes – which axes to log scale. Pass “x” for the x axis, “y” for the y axis, or “both”.

  • nice_format (bool) – whether to format numbers near 1 as regular numbers, instead of exponential notation. For example, passing True will show 1 as 1, while False will show 1 as 10^0. Defaults to True.

Returns:

None

import betterplotlib as bpl

bpl.set_style()

fig, axs = bpl.subplots(ncols=2, figsize=[12, 6])

for ax, nice_format in zip(axs, [True, False]):
    ax.log("both", nice_format)
    ax.set_limits(1e-3, 1e3, 1e-3, 1e3)
    ax.equal_scale()
    ax.add_labels(title=f"nice_format = {str(nice_format)}")
_images/full_api-38.png
make_ax_dark(grid=True, minor_ticks=False)[source]

Turns an axis into one with a dark background with white gridlines.

This will turn an axis into one with a slightly light gray background, and with solid white gridlines. All the axes spines are removed (so there isn’t any outline), and the ticks are removed too.

Parameters:
  • grid (bool) – Whether or not to draw the grid. Defaults to True.

  • minor_ticks (bool) – Whether or not to add minor ticks. They will be drawn as dotted lines, rather than solid lines in the axes space. If grid is False then this parameter does not matter.

Returns:

None

import betterplotlib as bpl
bpl.set_style()

fig, (ax0, ax1) = bpl.subplots(figsize=[12, 5], ncols=2)
ax1.make_ax_dark()
ax0.set_title("Regular")
ax1.set_title("Dark")
_images/full_api-39.png
plot(*args, **kwargs)[source]

A slightly improved plot function.

This is best used for plotting lines, while the scatter() function is best used for plotting points.

Currently all this does is make the lines thicker, which looks better. There isn’t any added functionality.

The parameters here are the exact same as they are for the regular plt.plot() or ax.plot() functions.

Parameters:
  • args – Additional arguments to pass to the plot function

  • kwargs – Additional keyword arguments to pass to the plot function

import numpy as np
import betterplotlib as bpl
import matplotlib.pyplot as plt
bpl.set_style()

xs = np.arange(0, 1, 0.01)
ys_1 = xs
ys_2 = xs**2

fig = plt.figure(figsize=[15, 7])
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122, projection="bpl")  # bpl subplot.

ax1.plot(xs, ys_1)
ax1.plot(xs, ys_2)

ax2.plot(xs, ys_1)
ax2.plot(xs, ys_2)

ax1.set_title("matplotlib")
ax2.set_title("betterplotlib")
_images/full_api-40.png
remove_labels(labels_to_remove)[source]

Removes the labels and tick marks from an axis border.

This is useful for making conceptual plots where the numbers on the axis don’t matter. Axes labels still work, also.

Note that this can break when used with the various remove_*() functions. Order matters with these calls, presumably due to something with the way matplotlib works under the hood. Mess around with it if you’re having trouble.

Parameters:

labels_to_remove (str) – location of labels to remove. Choose from: “both”, “x”, or “y”.

Returns:

None

Example:

import betterplotlib as bpl
import numpy as np
bpl.set_style()

xs = np.arange(0, 5, 0.1)
ys = xs**2

fig, ax = bpl.subplots()

ax.plot(xs, ys)

ax.remove_labels("y")
ax.remove_ticks("top")
ax.add_labels("Conceptual plot", "Axes labels still work")
_images/full_api-41.png
remove_spines(*spines_to_remove)[source]

Remove spines from the axis.

Spines are the lines on the side of the axes. In many situations, these are not needed, and are just junk. Calling this function will remove the specified spines from an axes object. Note that it does not remove the tick labels if they are visible for that axis.

Note that this function can mess up if you call this function multiple times with the same axes object, due to the way matplotlib works under the hood. I haven’t really tested it extensively (since I have never wanted to call it more than once), but I think the last function call is the one that counts. Calling this multiple times on the same axes would be pointless, though, since you can specify multiple axes in one call. If you really need to call it multiple times and it is breaking, let me know and I can try to fix it. This also can break when used with the various remove_*() functions. Order matters with these calls, for some reason.

Parameters:

spines_to_remove – The desired spines to remove. Can choose from “all”, “top”, “bottom”, “left”, or “right”.

Returns:

None

import betterplotlib as bpl
bpl.set_style()

fig, (ax0, ax1) = bpl.subplots(ncols=2, figsize=[10, 5])

ax0.plot([0, 1, 2], [0, 1, 2])
ax1.plot([0, 1, 2], [0, 1, 2])

ax0.remove_spines("top", "right")
ax1.remove_spines("all")

ax0.set_title("removed top/right spines")
ax1.set_title("removed all spines")
_images/full_api-42.png
remove_ticks(*ticks_to_remove)[source]

Removes ticks from the given locations.

In some situations, ticks aren’t needed or wanted. Note that this doesn’t remove the spine itself, or the labels on that axis.

Note that this can break when used with the various remove_*() functions. Order matters with these calls, presumably due to something with the way matplotlib works under the hood. Mess around with it if you’re having trouble.

Parameters:

ticks_to_remove – locations where ticks need to be removed from. Choose from: “all, “top”, “bottom”, “left”, or “right”, and pass in as many as you’d like

Returns:

None

import betterplotlib as bpl
bpl.set_style()

fig, (ax0, ax1) = bpl.subplots(ncols=2, figsize=[10, 5])

ax0.plot([0, 1, 2], [0, 1, 2])
ax1.plot([0, 1, 2], [0, 1, 2])

ax0.remove_ticks("top", "right")
ax1.remove_ticks("all")

ax0.set_title("removed top/right ticks")
ax1.set_title("removed all ticks")
_images/full_api-43.png
scatter(*args, **kwargs)[source]

Makes a scatter plot that looks nicer than the matplotlib default.

The call works just like a call to plt.scatter. It will set a few default parameters, but anything you pass in will override the default parameters. This function also uses the color cycle, unlike the default scatter.

It also automatically determines a guess at the proper alpha (transparency) of the points in the plot.

NOTE: the c parameter tells just the facecolor of the points, while color specifies the whole color of the point, including the edge line color. This follows the default matplotlib scatter implementation.

Parameters:
  • args – non-keyword arguments that will be passed on to the plt.scatter function. These will typically be the x and y lists.

  • kwargs – keyword arguments that will be passed on to plt.scatter.

Returns:

the output of the plt.scatter call is returned directly.

import betterplotlib as bpl
import numpy as np
bpl.set_style()

x = np.random.normal(0, scale=0.5, size=500)
y = np.random.normal(0, scale=0.5, size=500)

for dx in [0, 0.5, 1]:
    bpl.scatter(x + dx, y + dx)
bpl.equal_scale()
_images/full_api-44.png
set_limits(x_min=None, x_max=None, y_min=None, y_max=None, **kwargs)[source]

Set axes limits for both x and y axis at once.

Any additional kwargs will be passed on to the matplotlib functions that set the limits, so refer to that documentation to find the allowed parameters.

Parameters:
  • x_min (int, float) – minimum x value to be plotted

  • x_max (int, float) – maximum x value to be plotted

  • y_min (int, float) – minimum y value to be plotted

  • y_max (int, float) – maximum y value to be plotted

  • kwargs – Kwargs for the set_limits() functions.

Returns:

none.

Example:

import betterplotlib as bpl
import numpy as np
bpl.set_style()

xs = np.arange(0, 10, 0.01)
ys = np.cos(xs)

fig, [ax1, ax2] = bpl.subplots(ncols=2)

ax1.plot(xs, ys)

ax2.plot(xs, ys)
ax2.set_limits(0, 2*np.pi, -1.1, 1.1)
_images/full_api-45.png
set_ticks(axis, ticks, labels=None, minor=False)[source]

Set tick marks on an axis, with optional label names

This is just a wrapper around ax.xaxis.set_ticks(), but I always forget that syntax.

Parameters:
  • axis (str) – to put these ticks on either the “x” or “y” axis

  • ticks (list) – The list of locations to put ticks

  • labels (list) – The labels to put for each of the ticks passed in

  • minor (bool) – Whether these ticks should be major or minor ticks

import betterplotlib as bpl

bpl.set_style()

bpl.set_limits(1, 2, 1, 2)
bpl.equal_scale()
bpl.set_ticks("x", [1, 1.5, 1.7, 2.0])
bpl.set_ticks("y", [1, 1.2, 1.5, 2.0], ["A", "B", "C", "D"])
_images/full_api-46.png
import betterplotlib as bpl

bpl.set_style()

bpl.log("both")
bpl.set_limits(1, 10, 1, 10)
bpl.equal_scale()
bpl.set_ticks("x", [1, 10], ["A", "D"])
bpl.set_ticks("x", [3, 5], ["b", "c"], minor=True)
bpl.set_ticks("y", [1, 10])
bpl.set_ticks(
    "y",
    [1, 2, 3, 4, 5, 6, 7, 8, 9],
    ["", "2", "3", "", "5", "", "7", "", ""],
    minor=True,
)
_images/full_api-47.png
shaded_density(xs, ys, bin_size=None, smoothing=0, cmap='Greys', weights=None, log_xy=False, log_hist=False)[source]

Creates shaded regions showing the density.

Is essentially a 2D histogram, but supports smoothing. Under the hood, this uses the pcolormesh function in matplotlib.

Parameters:
  • xs (list, ndarray) – list of x values

  • ys (list, ndarray) – list of y values

  • bin_size (int, float, list) – Bin size to use for the underlying 2D histogram. This can either be a scalar, in which case the bin size will be the same in both the x dimensions, or else a two element list, where the first element will be the bin size in the x dimension, and the second will be the bin size in the y dimension.

  • smoothing (int, float, list) – Optional parameter that will smooth the shaded density. Pass in a nonzero value, which will be the standard deviation of the Gaussian kernel use to smooth the histogram. When using this, often choosing smaller bin sizes is advantageous to make a less grainy plot. Has the same format as padding and bin_size, so different smoothing kernels are possible in the x and y directions.

  • cmap (str) – The colormap to use for the shading

  • weights (list, np.ndarray) – A list containing weights for each data point. If these are not passed, all data points will be weighted equally.

  • log_xy – Whether or not to do the smoothing and bin creation in log space. This should be used if the plot will be done on log-scaled axes.Can either be a single bool, in which case the x and y scales will both be log (or not), or a two element array, where the first is whether the x axis is log, and the second is y. If this is used, the bin_size and smoothing parameters will be interpreted as dex, rather than raw values.

  • log_hist (bool) – Whether or not to use the log of the histogram values to compute the shading, or just the values of the histogram.

Returns:

output of the pcolormesh function call.

import betterplotlib as bpl
import numpy as np
bpl.set_style()

xs = np.concatenate(
    [np.random.normal(3, 2, 1000), np.random.normal(7, 2, 1000)]
)
ys = np.concatenate(
    [np.random.normal(7, 2, 1000), np.random.normal(3, 2, 1000)]
)

bpl.shaded_density(xs, ys, bin_size=0.01, smoothing=0.5, cmap="inferno")
bpl.set_limits(0, 10, 0, 10)
bpl.equal_scale()
_images/full_api-48.png
import betterplotlib as bpl
import numpy as np

bpl.set_style()

xs = np.concatenate(
    [np.random.normal(3, 2, 1000), np.random.normal(7, 2, 1000)]
)
ys = 10 ** np.concatenate(
    [np.random.normal(7, 2, 1000), np.random.normal(3, 2, 1000)]
)

fig, ax = bpl.subplots()
bpl.shaded_density(
    xs,
    ys,
    bin_size=0.01,
    smoothing=0.5,
    cmap="inferno",
    log_xy=[False, True],
)
ax.log("y")
bpl.set_limits(0, 10, 1, 1e10)
bpl.equal_scale()
_images/full_api-49.png
twin_axis(axis, new_ticks, label, old_to_new_func=None, new_to_old_func=None)[source]

Create a twin axis, where the new axis values are an arbitrary function of the old values.

This is used when you want to put two related quantities on the axis, for example distance/redshift in astronomy, where one isn’t a simple scaling of the other. If you want a simple linear or log scale, use the twin_axis_simple function. This one will create a new axis that is an arbitrary scale.

Parameters:
  • axis (str) – Whether the new axis labels will be on the “x” or “y” axis. If “x” is chosen this will place the markers on the top botder of the plot, while “y” will place the values on the left border of the plot. “x” and “y” are the only allowed values.

  • new_ticks (list, np.ndarray) – List of of locations (in the new data values) to place ticks. Any values outside the range of the plot will be ignored.

  • label (str) – The label given to the newly created axis.

  • old_to_new_func – Function that takes values on the original axis and transforms them to corresponding values on the soon-to-be created axis. Either this parameter or new_to_old_func can be used, but not both.

  • new_to_old_func – Function that takes values on the soon-to-be-created axis and transforms them to corresponding values on the original axis. Either this parameter or old_to_new_func can be used, but not both.

Returns:

New axis object that was created, containing the newly created labels.

import betterplotlib as bpl
bpl.set_style()

def square(x):
    return x**2

def cubed(x):
    return x**3

fig, ax = bpl.subplots(figsize=[5, 5], tight_layout=True)
ax.set_limits(0, 10, 0, 10.0001)  # to avoid floating point errors
ax.add_labels("x", "y")
ax.twin_axis("y", [0, 10, 30, 60, 100], "$y^2$", square)
ax.twin_axis("x", [0, 10, 100, 400, 1000], "$x^3$", cubed)
_images/full_api-50.png

Note that we had to be careful with floating point errors when one of the markers we want is exactly on the edge. Make the borders slightly larger to ensure that all labels fit on the plot.

There are two ways to give the funtion that transforms the values from one axis to the other. The parameter old_to_new_func (used as the last parameter in the plot above) takes values on the original axis and transforms them to values on the newly created axis. However, the parameter new_to_old_func does the inverse, taking values on the new axis and transforming them to the currently existing one. Only one of these two parameters can be provided. Identical plots can be created with either function, but due to specifics of the implementation, using the new_to_old_func parameter is slightly more computationally efficient. Here’s an example of an identical plot to the first created with new_to_old_func instead.

import betterplotlib as bpl

bpl.set_style()

def cube_root(x):
    return x ** (1.0 / 3.0)

fig, ax = bpl.subplots(figsize=[5, 5], tight_layout=True)
ax.set_limits(0, 10, 0, 10.0001)  # to avoid floating point errors
ax.add_labels("x", "y")
ax.twin_axis("y", [0, 10, 30, 60, 100], "$y^2$", new_to_old_func=np.sqrt)
ax.twin_axis(
    "x", [0, 10, 100, 400, 1000], "$x^3$", new_to_old_func=cube_root
)
_images/full_api-51.png

This function will ignore values for the ticks that are outside the limits of the plot. The following plot isn’t the most useful, since it could be done with the axis_twin_simple, but it gets the idea across.

import betterplotlib as bpl
bpl.set_style()

xs = np.logspace(0, 3, 100)

fig, ax = bpl.subplots(figsize=[5, 5], tight_layout=True)
ax.plot(xs, xs)
ax.log("both")
ax.add_labels("x", "y")
# extraneous values are ignored.
ax.twin_axis("x", [-1, 0, 1, 2, 3, 4, 5], "log(x)", np.log10)
ax.twin_axis("y", [-1, 0, 1, 2, 3, 4, 5], "log(y)", np.log10)
_images/full_api-52.png
twin_axis_simple(axis, lower_lim, upper_lim, label='', log=False)[source]

Creates a differently scaled axis on either the top or the left.

Note that this only does simple scalings of the new axes, which will still only be linear or log scaled axes. If you want a function that smartly places labels based on a function that takes one set of axes values to another (in a potentially nonlinear way), the other function I haven’t made will do that.

Parameters:
  • axis (str) – Where the new scaled axis will be placed. Must either be “x” or “y”.

  • lower_lim (float) – Value to be put on the left/bottom of the newly created axis.

  • upper_lim (float) – Value to be put on the right/top of the newly created axis.

  • label (str) – The label to put on this new axis.

  • log (bool) – Whether or not to log scale this axis.

Returns:

the new axes

import betterplotlib as bpl
bpl.set_style()

bpl.set_limits(0, 10, 0, 5)
bpl.add_labels("x", "y")
bpl.twin_axis_simple("x", 0, 100, "$10 x$")
bpl.twin_axis_simple("y", 1, 10**5, "$10^y$", log=True)
_images/full_api-53.png

Note that for a slightly more complicated version of this plot, say if we wanted the top x axis to be x^2 rather than 10x, the limits would still be the same, but since the new axis will always be a linear or log scale the new axis won’t represent the true relationship between the variables on the twin axes. See twin_axis for that.