Source code for apsg.plotting._roseplot

import pickle

import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import circmean, vonmises

from apsg.config import apsg_conf
from apsg.feature import feature_from_json
from apsg.math._vector import Axial2
from apsg.plotting._plot_artists import RosePlotArtistFactory
from apsg.plotting._styles import RosePlotStyle

__all__ = ["RosePlot"]


[docs] class RosePlot: """ ``RosePlot`` class for rose histogram plotting. Keyword Args: title (str): figure title. Default None title_kws (dict): dictionary of keyword arguments passed to matplotlib suptitle method. bins (int): Number of bins. Default 36 density (bool): Use density instead of counts. Default False pdf (bool): Plot Von Mises density function instead histogram. Default False pdf_res (int): Resolution of pdf. Default 901 kappa (float): Shape parameter of Von Mises pdf. Default 250 scaled (bool): Bins scaled by area instead value. Default False ticks (bool): show ticks. Default True grid (bool): show grid lines. Default False grid_kws (dict): Dict passed to Axes.grid. Default {} Other keyword arguments are passed to matplotlib plot. Note: Roseplot is weighted by magnitude of Direction or Vector2 features Examples: >>> v = vec2set.random_vonmises(position=120) >>> p = RosePlot(grid=False) >>> p.pdf(v) >>> p.bar(v, fc='none', ec='k', lw=1) >>> p.muci(v) >>> p.show() """ def __init__(self, **kwargs): self._kwargs = apsg_conf.roseplot.copy() self._kwargs.update((k, kwargs[k]) for k in self._kwargs.keys() & kwargs.keys()) self._artists = []
[docs] def clear(self): """Clear plot""" self._artists = []
def _draw_layout(self): # self.ax.format_coord = self.format_coord self.ax.set_theta_direction(-1) # type: ignore self.ax.set_theta_zero_location("N") # type: ignore self.ax.grid(self._kwargs["grid"], **self._kwargs["grid_kws"]) def _plot_artists(self): for artist in self._artists: plot_method = getattr(self, artist.roseplot_method) plot_method(*artist.args, **artist.kwargs)
[docs] def to_json(self): """Return rose plot as JSON dict.""" artists = [artist.to_json() for artist in self._artists] return dict(kwargs=self._kwargs, artists=artists)
[docs] @classmethod def from_json(cls, json_dict): """Create rose plot from JSON dict.""" s = cls(**json_dict["kwargs"]) s._artists = [roseartist_from_json(artist) for artist in json_dict["artists"]] return s
[docs] def save(self, filename): """ Save stereonet to pickle file Args: filename (str): name of picke file """ with open(filename, "wb") as f: pickle.dump(self.to_json(), f, pickle.HIGHEST_PROTOCOL)
[docs] @classmethod def load(cls, filename): """ Load stereonet from pickle file Args: filename (str): name of picke file """ with open(filename, "rb") as f: data = pickle.load(f) return cls.from_json(data)
def init_figure(self): self.fig = plt.figure( 0, figsize=apsg_conf.figsize, dpi=apsg_conf.dpi, facecolor=apsg_conf.facecolor, ) if hasattr(self.fig.canvas.manager, "set_window_title"): self.fig.canvas.manager.set_window_title("Rose diagram") def _render(self): self.ax = self.fig.add_subplot(111, polar=True) self._draw_layout() self._plot_artists() h, lbls = self.ax.get_legend_handles_labels() if h: self._lgd = self.ax.legend( h, lbls, prop={"size": 11}, borderaxespad=0, loc="center left", bbox_to_anchor=(1.1, 0.5), scatterpoints=1, numpoints=1, ) if self._kwargs["title"] is not None: self.fig.suptitle(self._kwargs["title"], **self._kwargs["title_kws"]) if self._kwargs["tight_layout"]: self.fig.tight_layout()
[docs] def render2fig(self, fig): """ Plot stereonet to already existing figure or subfigure Args: fig (Figure): A mtplotlib Figure artist """ self.fig = fig self._render()
[docs] def show(self): """Show rose plot.""" plt.close(0) # close previously rendered figure self.init_figure() self._render() plt.show()
[docs] def savefig(self, filename="roseplot.png", **kwargs): """ Save rose plot figure to graphics file Keyword Args: filename (str): filename All others kwargs are passed to matplotlib `Figure.savefig` """ plt.close(0) # close previously rendered figure self.init_figure() self._render() self.fig.savefig(filename, **kwargs) plt.close(0)
######################################## # STYLED PLOTTING # ########################################
[docs] def plot(self, style, *args): """ Plot features using apsg styles Args: style: apsg plotting style. See roseplot_styles *arg: any number of features to be plotted Note: Features in args are automatically filtered by style to accept only compatible features """ assert isinstance(style, RosePlotStyle), "Style must RosePlotStyle object" artist = style.create_artist(*args) if len(artist.args) > 0: self._artists.append(artist)
######################################## # PLOTTING METHODS # ########################################
[docs] def bar(self, *args, **kwargs): """ Plot rose histogram of angles Args: Vector2Set feature(s) Keyword Args: alpha (scalar): Set the alpha value. Default None color (color): Set the color of the point. Default None ec (color): Patch edge color. Default None fc (color): Patch face color. Default None ls (str): Line style string (only for multiple features). Default "-" lw (float): Set line width. Default 1.5 legend (bool): Whether to show legend. Default False """ try: artist = RosePlotArtistFactory.create_bar(*args, **kwargs) self._artists.append(artist) except TypeError as err: print(err)
[docs] def pdf(self, *args, **kwargs): """ Plot Von Mises probability density function from angles Args: Vector2Set feature(s) Keyword Args: alpha (scalar): Set the alpha value. Default None color (color): Set the color of the point. Default None ec (color): Patch edge color. Default None fc (color): Patch face color. Default None ls (str): Line style string (only for multiple features). Default "-" lw (float): Set line width. Default 1.5 legend (bool): Whether to show legend. Default False """ try: artist = RosePlotArtistFactory.create_pdf(*args, **kwargs) self._artists.append(artist) except TypeError as err: print(err)
[docs] def muci(self, *args, **kwargs): """ Plot circular mean with bootstrapped confidence interval Args: Vector2Set feature(s) Keyword Args: alpha (scalar): Set the alpha value. Default None color (color): Set the color of the point. Default None ls (str): Line style string (only for multiple features). Default "-" lw (float): Set line width. Default 1.5 confidence_level (float): Confidence interval. Default 95 n_resamples (int): Number of bootstrapped samples. Default 9999 """ try: artist = RosePlotArtistFactory.create_muci(*args, **kwargs) self._artists.append(artist) except TypeError as err: print(err)
######################################## # PLOTTING ROUTINES # ######################################## def _bar(self, *args, **kwargs): bottom = np.zeros_like(self._kwargs["bins"]) width = 2 * np.pi / self._kwargs["bins"] legend = kwargs.pop("legend") for arg in args: if issubclass(arg.__feature_class__, Axial2): ang = np.concatenate((arg.direction % 360, (arg.direction + 180) % 360)) weights = np.concatenate((abs(arg), abs(arg))) else: ang = arg.direction % 360 weights = abs(arg) num, bin_edges = np.histogram( np.radians(ang), bins=self._kwargs["bins"] + 1, range=(-width / 2, 2 * np.pi + width / 2), weights=weights, density=self._kwargs["density"], ) num[0] += num[-1] num = num[:-1] bin_centre = (bin_edges[1:-1] + bin_edges[:-2]) / 2 if self._kwargs["scaled"]: num = np.sqrt(num) if legend: kwargs["label"] = arg.label() self.ax.bar(bin_centre, num, width=width, bottom=bottom, **kwargs) else: self.ax.bar(bin_centre, num, width=width, bottom=bottom, **kwargs) bottom = bottom + num def _pdf(self, *args, **kwargs): bottom = np.zeros_like(self._kwargs["pdf_res"]) legend = kwargs.pop("legend") theta = np.linspace(-np.pi, np.pi, self._kwargs["pdf_res"]) for arg in args: ang = arg.direction % 360 weights = abs(arg) weights = len(weights) * weights / sum(weights) radii = np.zeros_like(theta) if issubclass(arg.__feature_class__, Axial2): for a, weight in zip(ang, weights): radii += ( weight * vonmises.pdf(theta, self._kwargs["kappa"], loc=np.radians(a)) / 2 ) radii += ( weight * vonmises.pdf( theta, self._kwargs["kappa"], loc=np.radians(a + 180) ) / 2 ) else: for a in ang: radii += vonmises.pdf( theta, self._kwargs["kappa"], loc=np.radians(a) ) radii /= len(ang) if self._kwargs["scaled"]: radii = np.sqrt(radii) if legend: kwargs["label"] = arg.label() self.ax.fill_between(theta, bottom + radii, y2=bottom, **kwargs) else: self.ax.fill_between(theta, bottom + radii, y2=bottom, **kwargs) bottom = bottom + radii def _muci(self, *args, **kwargs): conflevel = kwargs.pop("confidence_level") n_resamples = kwargs.pop("n_resamples") for arg in args: radii = [] p = 0 ang = np.radians(arg.direction) if issubclass(arg.__feature_class__, Axial2): mu = circmean(2 * ang) / 2 ang_shift = ang + np.pi / 2 - mu bsmu = [ circmean(np.random.choice(2 * ang_shift, size=len(ang_shift))) for i in range(n_resamples) ] low = np.percentile(bsmu, 100 - conflevel) / 2 + mu - np.pi / 2 high = np.percentile(bsmu, conflevel) / 2 + mu - np.pi / 2 for a in arg.direction: p += vonmises.pdf(mu, self._kwargs["kappa"], loc=np.radians(a)) / 2 p += ( vonmises.pdf(mu, self._kwargs["kappa"], loc=np.radians(a + 180)) / 2 ) else: mu = circmean(ang) ang_shift = ang + np.pi - mu bsmu = [ circmean(np.random.choice(ang_shift, size=len(ang_shift))) for i in range(n_resamples) ] low = np.percentile(bsmu, (100 - conflevel) / 2) + mu - np.pi high = np.percentile(bsmu, 100 - (100 - conflevel) / 2) + mu - np.pi for a in arg.direction: p += vonmises.pdf(mu, self._kwargs["kappa"], loc=np.radians(a)) radii.append(p / len(arg)) if self._kwargs["scaled"]: radii = np.sqrt(radii) mur = 1.1 * sum(radii) ci_angles = np.linspace(low, high, int(5 * np.degrees(high - low))) if issubclass(arg.__feature_class__, Axial2): self.ax.plot([mu, mu + np.pi], [mur, mur], **kwargs) self.ax.plot(ci_angles, mur * np.ones_like(ci_angles), **kwargs) self.ax.plot(ci_angles + np.pi, mur * np.ones_like(ci_angles), **kwargs) else: self.ax.plot([0, mu], [0, mur], **kwargs) self.ax.plot(ci_angles, mur * np.ones_like(ci_angles), **kwargs)
def roseartist_from_json(obj_json): args = tuple([feature_from_json(arg_json) for arg_json in obj_json["args"]]) return getattr(RosePlotArtistFactory, obj_json["factory"])( *args, **obj_json["kwargs"] )