Source code for embedded_voting.embeddings.embeddings

# -*- coding: utf-8 -*-
"""
This file is part of Embedded Voting.
"""
from itertools import combinations
import numpy as np
import matplotlib.pyplot as plt
from embedded_voting.utils.miscellaneous import normalize, max_angular_dilatation_factor, volume_parallelepiped
from embedded_voting.utils.plots import create_ternary_plot, create_3d_plot


# noinspection PyUnresolvedReferences
[docs]class Embeddings(np.ndarray): """ Embeddings of the voters. Parameters ---------- positions : np.ndarray or list or Embeddings The embeddings of the voters. Its dimensions are :attr:`n_voters`, :attr:`n_dim`. norm: bool If True, normalize the embeddings. Attributes ---------- n_voters : int The number of voters in the ratings. n_dim : int The number of dimensions of the voters' embeddings. Examples -------- >>> embeddings = Embeddings([[1, 0], [0, 1], [0.5, 0.5]], norm=True) >>> embeddings.n_voters 3 >>> embeddings.n_dim 2 >>> embeddings.voter_embeddings(0) array([1., 0.]) """ def __new__(cls, positions, norm): """ >>> embeddings = Embeddings([[1, 2], [3, 4]], norm=False) >>> embeddings.n_sing_val_ = 42 >>> embeddings_2 = Embeddings(embeddings, norm=False) >>> embeddings_2.n_sing_val_ 42 """ obj = np.asarray(positions).view(cls) if norm: obj = obj / np.linalg.norm(obj, axis=1)[:, np.newaxis] if hasattr(positions, '__dict__'): for key, val in positions.__dict__.items(): setattr(obj, key, getattr(positions, key)) return obj def __array_finalize__(self, obj): if obj is None: return if len(self.shape) == 2: self.n_voters, self.n_dim = self.shape
[docs] def copy(self, order='C'): result = super().copy(order=order) for key, val in self.__dict__.items(): setattr(result, key, getattr(self, key)) return result
def voter_embeddings(self, i): return np.array(self[i:i + 1, :])[0]
[docs] def times_ratings_candidate(self, ratings_candidate): """ This method computes the embeddings multiplied by the ratings given by the voters to a given candidate. For each voter, its embeddings are multiplied by the given rating. Parameters ---------- ratings_candidate: np.ndarray The vector of ratings given by the voters to a given candidate. Return ------ Embddings A new Embeddings object, where the embedding of each voter is multiplied by the rating she assigned to the candidate. Examples -------- >>> embeddings = Embeddings(np.array([[1, 0], [0, 1], [0.5, 0.5]]), norm=False) >>> embeddings.times_ratings_candidate(np.array([.8, .5, .4])) Embeddings([[0.8, 0. ], [0. , 0.5], [0.2, 0.2]]) """ return np.multiply(self, ratings_candidate[::, np.newaxis])
[docs] def normalized(self): """ Normalize the embeddings of the voters so the Euclidean norm of every embedding is 1. Return ------ Embeddings A new Embeddings object with the normalized embeddings. Examples -------- >>> embeddings = Embeddings(-np.array([[.5,.9,.4],[.4,.7,.5],[.4,.2,.4]]), norm=False) >>> embeddings Embeddings([[-0.5, -0.9, -0.4], [-0.4, -0.7, -0.5], [-0.4, -0.2, -0.4]]) >>> embeddings.normalized() Embeddings([[-0.45267873, -0.81482171, -0.36214298], [-0.42163702, -0.73786479, -0.52704628], [-0.66666667, -0.33333333, -0.66666667]]) """ return self / np.linalg.norm(self, axis=1)[:, np.newaxis]
[docs] def get_center(self, approx=True): """ Return the center direction of the embeddings. For this method, we work on the normalized embeddings. Cf. :meth:`normalized`. With `approx` set to False, we use an exponential algorithm in `n_dim.` If `r` is the rank of the embedding matrix, we first find the `r` voters with maximal determinant (in absolute value), i.e. whose associated parallelepiped has the maximal volume (e.g. in two dimensions, it means finding the two vectors with maximal angle). Then the result is the mean of the embeddings of these voters, normalized in the sense of the Euclidean norm. With `approx` set to True, we use a polynomial algorithm: we simply take the mean of the embeddings of all the voters, normalized in the sense of the Euclidean norm. Parameters ---------- approx : bool Whether the computation is approximate. Return ------ np.ndarray The normalized position of the center vector. Size: :attr:`n_dim`. Examples -------- >>> embeddings = Embeddings([[1, 0], [0, 1], [.5, .5], [.7, .3]], norm=True) >>> embeddings.get_center(approx=False) array([0.70710678, 0.70710678]) >>> embeddings = Embeddings([[1, 0], [0, 1], [.5, .5], [.7, .3]], norm=False) >>> embeddings.get_center(approx=False) array([0.70710678, 0.70710678]) >>> embeddings = Embeddings([[1, 0], [0, 1], [.5, .5], [.7, .3]], norm=True) >>> embeddings.get_center(approx=True) array([0.78086524, 0.62469951]) """ self_normalized = self.normalized() if approx: return normalize(np.array(self_normalized.sum(axis=0))) else: matrix_rank = np.linalg.matrix_rank(self_normalized) subset_of_voters = max( combinations(range(self.n_voters), matrix_rank), key=lambda subset: volume_parallelepiped(self_normalized[subset, :]) ) embeddings_subset = self_normalized[subset_of_voters, :] mean = normalize(np.array(embeddings_subset.sum(axis=0))) return mean
[docs] def dilated_aux(self, center, k): """ Dilate the embeddings of the voters. For each `vector` of the embedding, we apply a "spherical dilatation" that moves `vector` by multiplying the angle between `center` and `vector` by a given dilatation factor. More formally, for each `vector` of the embedding, there exists a unit vector `unit_orthogonal` and an angle `theta in [0, pi/2]` such that `vector = norm(vector) * (cos(theta) * center + sin(theta) * unit_orthogonal)`. Then the image of `vector` is `norm(vector) * (cos(k * theta) * center + sin(k * theta) * unit_orthogonal)`. Parameters ---------- center: np.ndarray Unit vector: center of the dilatation. k: float Angular dilatation factor. Return ------ Embeddings A new Embeddings object with the dilated embeddings. Examples -------- >>> embeddings = Embeddings([[1, 0], [1, 1]], norm=True) >>> dilated_embeddings = embeddings.dilated_aux(center=np.array([1, 0]), k=2) >>> np.round(dilated_embeddings, 4) array([[1., 0.], [0., 1.]]) >>> embeddings = Embeddings([[1, 0], [1, 1]], norm=False) >>> dilated_embeddings = embeddings.dilated_aux(center=np.array([1, 0]), k=2) >>> np.abs(np.round(dilated_embeddings, 4)) # Abs for rounding errors array([[1. , 0. ], [0. , 1.4142]]) """ new_positions = np.zeros((self.n_voters, self.n_dim)) for i in range(self.n_voters): vector = self.voter_embeddings(i) norm_vector = np.linalg.norm(vector) scalar_product = vector @ center theta = np.arccos(scalar_product / norm_vector) if theta == 0: new_positions[i] = vector else: vector_collinear = scalar_product * center vector_orthogonal = vector - vector_collinear unit_orthogonal = normalize(vector_orthogonal) new_positions[i] = norm_vector * (center * np.cos(k * theta) + unit_orthogonal * np.sin(k * theta)) return Embeddings(new_positions, norm=False)
[docs] def dilated(self, approx=True): """ Dilate the embeddings of the voters so that they take more space. The `center` is computed with :meth:`get_center`. The angular dilatation factor is such that after transformation, the maximum angle between the center and an embedding vector will be pi / 4. Parameters ---------- approx : bool Passed to :meth:`get_center` in order to compute the center of the voters' embeddings. Return ------ Embeddings A new Embeddings object with the dilated embeddings. Examples -------- >>> embeddings = Embeddings(np.array([[.5,.4,.4],[.4,.4,.5],[.4,.5,.4]]), norm=True) >>> embeddings Embeddings([[0.66226618, 0.52981294, 0.52981294], [0.52981294, 0.52981294, 0.66226618], [0.52981294, 0.66226618, 0.52981294]]) >>> embeddings.dilated() Embeddings([[0.98559856, 0.11957316, 0.11957316], [0.11957316, 0.11957316, 0.98559856], [0.11957316, 0.98559856, 0.11957316]]) Note that the resulting embedding may not be in the positive orthant, even if the original embedding is: >>> embeddings = Embeddings([[1, 0], [.7, .7]], norm=True) >>> embeddings.dilated() Embeddings([[ 0.92387953, -0.38268343], [ 0.38268343, 0.92387953]]) >>> Embeddings([[1, 0]], norm=True).dilated() Embeddings([[1., 0.]]) """ center = self.get_center(approx=approx) min_scalar_product = min([ np.dot(normalize(self.voter_embeddings(i)), center) for i in range(self.n_voters) ]) theta_max = np.arccos(min_scalar_product) if theta_max == 0: # all embeddings are aligned with `center`. return self.copy() k = np.pi / (4 * theta_max) return self.dilated_aux(center=center, k=k)
[docs] def dilated_new(self, approx=True): """ Dilate the embeddings of the voters so that they take more space in the positive orthant. The `center` is computed with :meth:`get_center`. The angular dilatation factor the largest possible so that all vectors stay in the positive orthant. Cf. :func:`~embedded_voting.utils.miscellaneous.max_angular_dilatation_factor`. Parameters ---------- approx : bool Passed to :meth:`get_center` in order to compute the center of the voters' embeddings. Return ------ Embeddings A new Embeddings object with the dilated embeddings. Examples -------- >>> embeddings = Embeddings(np.array([[.5,.4,.4],[.4,.4,.5],[.4,.5,.4]]), norm=True) >>> embeddings Embeddings([[0.66226618, 0.52981294, 0.52981294], [0.52981294, 0.52981294, 0.66226618], [0.52981294, 0.66226618, 0.52981294]]) >>> dilated_embeddings = embeddings.dilated_new() >>> np.abs(np.round(dilated_embeddings, 4)) array([[1., 0., 0.], [0., 0., 1.], [0., 1., 0.]]) >>> embeddings = Embeddings([[1, 0], [.7, .7]], norm=True) >>> dilated_embeddings = embeddings.dilated_new() >>> np.abs(np.round(dilated_embeddings, 4)) array([[1. , 0. ], [0.7071, 0.7071]]) >>> embeddings = Embeddings([[2, 1], [100, 200]], norm=False) >>> dilated_embeddings = embeddings.dilated_new() >>> np.round(dilated_embeddings, 4) array([[ 2.2361, 0. ], [ 0. , 223.6068]]) """ center = self.get_center(approx=approx) k = min([ max_angular_dilatation_factor(vector=np.array(vector), center=center) for vector in self.normalized() ]) if np.isinf(k): return self.copy() return self.dilated_aux(center=center, k=k)
[docs] def recentered(self, approx=True): """ Recenter the embeddings so that their new center is [1, ..., 1]. Parameters ---------- approx : bool Passed to :meth:`get_center` in order to compute the center of the voters' embeddings. Return ------ Embeddings A new Embeddings object with the recentered embeddings. Examples -------- >>> embeddings = Embeddings(-np.array([[.5,.9,.4],[.4,.7,.5],[.4,.2,.4]]), norm=True) >>> embeddings Embeddings([[-0.45267873, -0.81482171, -0.36214298], [-0.42163702, -0.73786479, -0.52704628], [-0.66666667, -0.33333333, -0.66666667]]) >>> embeddings.recentered() Embeddings([[0.40215359, 0.75125134, 0.52334875], [0.56352875, 0.6747875 , 0.47654713], [0.70288844, 0.24253193, 0.66867489]]) >>> embeddings = Embeddings([[1, 0], [np.sqrt(3)/2, 1/2], [1/2, np.sqrt(3)/2]], norm=True) >>> embeddings Embeddings([[1. , 0. ], [0.8660254, 0.5 ], [0.5 , 0.8660254]]) >>> embeddings.recentered(approx=False) Embeddings([[0.96592583, 0.25881905], [0.70710678, 0.70710678], [0.25881905, 0.96592583]]) """ old_center = self.get_center(approx=approx) target_center = normalize(np.ones(self.n_dim)) scalar_product = old_center @ target_center if scalar_product == -1: return - self elif scalar_product == 1: return self.copy() theta = np.arccos(scalar_product) target_center_collinear = scalar_product * old_center target_center_orthogonal = target_center - target_center_collinear unit_orthogonal = target_center_orthogonal / np.linalg.norm(target_center_orthogonal) # (old_center, unit_orthogonal) is an orthogonal basis of the plane (old_center, target_center). rotation_matrix = ( (np.cos(theta) - 1) * ( np.outer(old_center, old_center) + np.outer(unit_orthogonal, unit_orthogonal) ) + np.sin(theta) * ( np.outer(unit_orthogonal, old_center) - np.outer(old_center, unit_orthogonal) ) + np.eye(self.n_dim) ) new_positions = self @ rotation_matrix.T return Embeddings(new_positions, norm=False)
[docs] def recentered_and_dilated(self, approx=True): """ Recenter and dilate. This is just a shortcut for the (common) operation :meth:`recentered`, then :meth:`dilated_new`. Parameters ---------- approx : bool Passed to :meth:`get_center` in order to compute the center of the voters' embeddings. Returns ------- Embeddings A new Embeddings object with the recentered and dilated embeddings. Examples -------- >>> embeddings = Embeddings([[1, 0], [np.sqrt(3)/2, 1/2], [1/2, np.sqrt(3)/2]], norm=True) >>> embeddings Embeddings([[1. , 0. ], [0.8660254, 0.5 ], [0.5 , 0.8660254]]) >>> new_embeddings = embeddings.recentered_and_dilated(approx=False) >>> np.abs(np.round(new_embeddings, 4)) array([[1. , 0. ], [0.7071, 0.7071], [0. , 1. ]]) """ return self.recentered(approx=approx).dilated_new(approx=approx)
[docs] def mixed_with(self, other, intensity): """ Mix this embedding with another one. Parameters ---------- other : Embeddings Another embedding with the name number of voters and same number of dimensions. intensity : float Must be in [0, 1]. Returns ------- Embeddings A new Embeddings object with the mixed embeddings. Examples -------- For a given voter, the direction of the final embedding is an "angular barycenter" between the original direction and the direction in `other`, with mixing parameter `intensity`: >>> embeddings = Embeddings([[1, 0]], norm=True) >>> other_embeddings = Embeddings([[0, 1]], norm=True) >>> embeddings.mixed_with(other_embeddings, intensity=1/3) Embeddings([[0.8660254, 0.5 ]]) For a given voter, the norm of the final embedding is a barycenter between the original norm and the norm in `other`, with mixing parameter `intensity`: >>> embeddings = Embeddings([[1, 0]], norm=False) >>> other_embeddings = Embeddings([[5, 0]], norm=False) >>> embeddings.mixed_with(other_embeddings, intensity=1/4) Embeddings([[2., 0.]]) """ norms_self = np.linalg.norm(self, axis=1) norms_other = np.linalg.norm(other, axis=1) self_normalized = self / norms_self[:, np.newaxis] other_dot_products_self_normalized = np.sum(other * self_normalized, axis=1) other_collinear = other_dot_products_self_normalized[:, np.newaxis] * self_normalized other_orthogonal = other - other_collinear norms_other_orthogonal = np.linalg.norm(other_orthogonal, axis=1) unit_orthogonal = other_orthogonal / np.where( norms_other_orthogonal > 0, norms_other_orthogonal, 1 )[:, np.newaxis] thetas = np.arccos(other_dot_products_self_normalized / norms_other) norms = (1 - intensity) * norms_self + intensity * norms_other directions = ( np.cos(intensity * thetas)[:, np.newaxis] * self_normalized + np.sin(intensity * thetas)[:, np.newaxis] * unit_orthogonal ) return norms[:, np.newaxis] * directions
def _plot_3d(self, fig, dim, plot_position=None): """ Plot a figure of the embeddings on a 3D space using matplotlib. Parameters ---------- fig : matplotlib.figure The figure on which we do the plot. dim : list The 3 dimensions we are using for our plot. plot_position : list The position of the plot on the figure. Should be of the form ``[n_rows, n_columns, position]``. Return ------ matplotlib.ax The matplotlib ax with the figure, if you want to add something to it. """ ax = create_3d_plot(fig, plot_position) for v in self: x1 = v[dim[0]] x2 = v[dim[1]] x3 = v[dim[2]] ax.plot([0, x1], [0, x2], [0, x3], color=(x1**2 * 0.8, x2**2 * 0.8, x3**2 * 0.8), alpha=0.4) ax.scatter([x1], [x2], [x3], color='k', s=1) return ax def _plot_ternary(self, fig, dim, plot_position=None): """ Plot a figure of the embeddings on a 2D space representing the surface of the unit sphere on the non-negative orthant. Parameters ---------- fig : matplotlib figure The figure on which we add the plot. dim : list The 3 dimensions we are using for our plot. plot_position : list The position of the plot on the figure. Should be of the form ``[n_rows, n_columns, position]``. Return ------ matplotlib ax The matplotlib ax with the figure, if you want to add something to it. """ tax = create_ternary_plot(fig, plot_position) for v in self: x1 = v[dim[0]] x2 = v[dim[2]] x3 = v[dim[1]] vec = [x1, x2, x3] tax.scatter([normalize(vec)**2], color=(x1**2 * 0.8, x3**2 * 0.8, x2**2 * 0.8), alpha=0.9, s=30) return tax
[docs] def plot(self, plot_kind="3D", dim: list = None, fig=None, plot_position=None, show=True): """ Plot the embeddings of the voters, either on a 3D plot, or on a ternary plot. Only three dimensions can be represented. Parameters ---------- plot_kind : str The kind of plot we want to show. Can be ``'3D'`` or ``'ternary'``. dim : list A list of length 3 containing the three dimensions of the embeddings we want to plot. All elements of this list should be lower than :attr:`n_dim`. By default, it is set to ``[0, 1, 2]``. fig : matplotlib figure The figure on which we add the plot. The default figure is a `8 x 8` matplotlib figure. plot_position : list List of length 3 containing the matplotlib position ``[n_rows, n_columns, position]``. By default, it is set to ``[1, 1, 1]``. show : bool If True, display the figure at the end of the function. Return ------ matplotlib ax The matplotlib ax with the figure, if you want to add something to it. """ if dim is None: dim = [0, 1, 2] elif len(dim) != 3: raise ValueError("The number of dimensions should be 3") if fig is None: fig = plt.figure(figsize=(8, 8)) if plot_kind == "3D": ax = self._plot_3d(fig, dim, plot_position) elif plot_kind == "ternary": ax = self._plot_ternary(fig, dim, plot_position) else: raise ValueError("plot_kind should '3D' or 'ternary'") ax.set_title("Embeddings of voters on dimensions (%i,%i,%i)" % (dim[0], dim[1], dim[2]), fontsize=24) if show: plt.show() # pragma: no cover return ax
def _plot_ratings_candidate_3d(self, ratings_candidate, fig, plot_position, dim): """ Plot the matrix associated to a candidate in a 3D space. The embedding of each voter is multiplied by the rating she assigned to the candidate. Parameters ---------- ratings_candidate : np.ndarray The rating each voters assigned to the given candidate. Should be of length :attr:`n_voters`. fig : matplotlib figure The figure on which we add the plot. plot_position : list The position of the plot on the figure. Should be of the form ``[n_rows, n_columns, position]``. dim : list The 3 dimensions we are using for our plot. Return ------ matplotlib ax The matplotlib ax with the figure, if you want to add something to it. """ ax = create_3d_plot(fig, plot_position) for (v, s) in zip(np.array(self), ratings_candidate): x1 = v[dim[0]] x2 = v[dim[1]] x3 = v[dim[2]] ax.plot([0, s * x1], [0, s * x2], [0, s * x3], color=(x1**2 * 0.8, x2**2 * 0.8, x3**2 * 0.8), alpha=0.4) return ax def _plot_ratings_candidate_ternary(self, ratings_candidate, fig, plot_position, dim): """ Plot the matrix associated to a candidate on a 2D space representing the sphere in the non-negative orthant. The embedding of each voter is multiplied by the rating she assigned to the candidate. Parameters ---------- ratings_candidate : np.ndarray The rating each voters assigned to the given candidate. Should be of length :attr:`n_voters`. fig : matplotlib figure The figure on which we add the plot. plot_position : list The position of the plot on the figure. Should be of the form ``[n_rows, n_columns, position]``. dim : list The 3 dimensions we are using for our plot. Return ------ matplotlib ax The matplotlib ax with the figure, if you want to add something to it. """ tax = create_ternary_plot(fig, plot_position) for (v, s) in zip(np.array(self), ratings_candidate): x1 = v[dim[0]] x2 = v[dim[1]] x3 = v[dim[2]] vec = [x1, x2, x3] tax.scatter([normalize(vec)**2], color=(x1**2 * 0.8, x3**2 * 0.8, x2**2 * 0.8), alpha=0.7, s=max(s * 50, 1)) return tax
[docs] def plot_ratings_candidate(self, ratings_candidate, title="", plot_kind="3D", dim: list = None, fig=None, plot_position=None, show=True): """ Plot the matrix associated to a candidate. The embedding of each voter is multiplied by the rating she assigned to the candidate. Parameters ---------- ratings_candidate : np.ndarray The rating each voters assigned to the given candidate. Should be of length :attr:`n_voters`. title : str Title of the figure. plot_kind : str The kind of plot we want to show. Can be ``'3D'`` or ``'ternary'``. fig : matplotlib figure The figure on which we add the plot. plot_position : list The position of the plot on the figure. Should be of the form ``[n_rows, n_columns, position]``. dim : list The 3 dimensions we are using for our plot. By default, it is set to ``[0, 1, 2]``. show : bool If True, display the figure at the end of the function. Return ------ matplotlib ax The matplotlib ax with the figure, if you want to add something to it. """ if dim is None: dim = [0, 1, 2] elif len(dim) != 3: raise ValueError("The number of dimensions should be 3") if fig is None: fig = plt.figure(figsize=(8, 8)) if plot_kind == "3D": ax = self._plot_ratings_candidate_3d(ratings_candidate, fig, plot_position, dim) elif plot_kind == "ternary": ax = self._plot_ratings_candidate_ternary(ratings_candidate, fig, plot_position, dim) else: raise ValueError("plot_kind should '3D' or 'ternary'") ax.set_title(title, fontsize=16) if show: plt.show() # pragma: no cover return ax
[docs] def plot_candidate(self, ratings, candidate, plot_kind="3D", dim: list = None, fig=None, plot_position=None, show=True): """ Plot the matrix associated to a candidate. The embedding of each voter is multiplied by the rating she assigned to the candidate. Parameters ---------- ratings: np.ndarray Matrix of ratings given by voters to the candidates. candidate : int The candidate for which we want to show the ratings. Should be lower than :attr:`n_candidates` of ratings. plot_kind : str The kind of plot we want to show. Can be ``'3D'`` or ``'ternary'``. fig : matplotlib figure The figure on which we add the plot. plot_position : list The position of the plot on the figure. Should be of the form ``[n_rows, n_columns, position]``. dim : list The 3 dimensions we are using for our plot. By default, it is set to ``[0, 1, 2]``. show : bool If True, display the figure at the end of the function. Return ------ matplotlib ax The matplotlib ax with the figure, if you want to add something to it. """ return self.plot_ratings_candidate(ratings[::, candidate], title="Candidate %i" % (candidate + 1), plot_kind=plot_kind, dim=dim, fig=fig, plot_position=plot_position, show=show)
[docs] def plot_candidates(self, ratings, plot_kind="3D", dim: list = None, list_candidates=None, list_titles=None, row_size=5, show=True): """ Plot the matrix associated to a candidate for every candidate in a list of candidates. Parameters ---------- ratings: Ratings Ratings given by voters to candidates. plot_kind : str The kind of plot we want to show. Can be ``'3D'`` or ``'ternary'``. dim : list The 3 dimensions we are using for our plot. By default, it is set to ``[0, 1, 2]``. list_candidates : int list The list of candidates we want to plot. Should contains integer lower than :attr:`n_candidates`. By default, we plot every candidates. list_titles : str list Contains the title of the plots. Should be the same length than `list_candidates`. row_size : int Number of subplots by row. By default, it is set to 5 plots by rows. show : bool If True, display the figure at the end of the function. """ if list_candidates is None: list_candidates = range(ratings.shape[1]) if list_titles is None: list_titles = ["Candidate %i" % c for c in list_candidates] else: list_titles = ["%s " % t for t in list_titles] n_candidates = len(list_candidates) n_rows = (n_candidates - 1) // row_size + 1 fig = plt.figure(figsize=(5 * row_size, n_rows * 5)) position = [n_rows, row_size, 1] for candidate, title in (zip(list_candidates, list_titles)): self.plot_ratings_candidate(ratings[::, candidate], title=title, plot_kind=plot_kind, dim=dim, fig=fig, plot_position=position, show=False) position[2] += 1 if show: plt.show() # pragma: no cover