Source code for data_morph.shapes.points

"""Shapes that are composed of points."""

import itertools
from numbers import Number

import numpy as np

from ..data.dataset import Dataset
from .bases.point_collection import PointCollection


[docs] class DotsGrid(PointCollection): """ Class representing a 3x3 grid of dots. .. plot:: :scale: 75 :caption: This shape is generated using the panda dataset. from data_morph.data.loader import DataLoader from data_morph.shapes.points import DotsGrid _ = DotsGrid(DataLoader.load_dataset('panda')).plot() Parameters ---------- dataset : Dataset The starting dataset to morph into other shapes. """ def __init__(self, dataset: Dataset) -> None: xlow, xhigh = dataset.df.x.quantile([0.05, 0.95]).tolist() ylow, yhigh = dataset.df.y.quantile([0.05, 0.95]).tolist() xmid = (xhigh + xlow) / 2 ymid = (yhigh + ylow) / 2 super().__init__( *list(itertools.product([xlow, xmid, xhigh], [ylow, ymid, yhigh])) ) def __str__(self) -> str: return 'dots'
[docs] class DownParabola(PointCollection): """ Class for the down parabola shape. .. plot:: :scale: 75 :caption: This shape is generated using the panda dataset. from data_morph.data.loader import DataLoader from data_morph.shapes.points import DownParabola _ = DownParabola(DataLoader.load_dataset('panda')).plot() Parameters ---------- dataset : Dataset The starting dataset to morph into other shapes. """ def __init__(self, dataset: Dataset) -> None: x_bounds = dataset.data_bounds.x_bounds xmin, xmax = x_bounds xmid = xmax - x_bounds.range / 2 x_offset = x_bounds.range / 10 xmin += x_offset xmax -= x_offset ymin, ymax = dataset.data_bounds.y_bounds poly = np.polynomial.Polynomial.fit([xmin, xmid, xmax], [ymin, ymax, ymin], 2) super().__init__(*np.stack(poly.linspace(), axis=1)) def __str__(self) -> str: return 'down_parab'
[docs] class Heart(PointCollection): """ Class for the heart shape. .. plot:: :scale: 75 :caption: This shape is generated using the panda dataset. from data_morph.data.loader import DataLoader from data_morph.shapes.points import Heart _ = Heart(DataLoader.load_dataset('panda')).plot() Parameters ---------- dataset : Dataset The starting dataset to morph into other shapes. Notes ----- The formula for the heart shape is inspired by `Heart Curve <https://mathworld.wolfram.com/HeartCurve.html>`_: Weisstein, Eric W. "Heart Curve." From `MathWorld <https://mathworld.wolfram.com/>`_ --A Wolfram Web Resource. https://mathworld.wolfram.com/HeartCurve.html """ def __init__(self, dataset: Dataset) -> None: x_bounds = dataset.data_bounds.x_bounds y_bounds = dataset.data_bounds.y_bounds x_shift = sum(x_bounds) / 2 y_shift = sum(y_bounds) / 2 t = np.linspace(-3, 3, num=80) x = 16 * np.sin(t) ** 3 y = 13 * np.cos(t) - 5 * np.cos(2 * t) - 2 * np.cos(3 * t) - np.cos(4 * t) # scale by the half the widest width of the heart scale_factor = (x_bounds[1] - x_shift) / 16 super().__init__( *np.stack([x * scale_factor + x_shift, y * scale_factor + y_shift], axis=1) )
[docs] class LeftParabola(PointCollection): """ Class for the left parabola shape. .. plot:: :scale: 75 :caption: This shape is generated using the panda dataset. from data_morph.data.loader import DataLoader from data_morph.shapes.points import LeftParabola _ = LeftParabola(DataLoader.load_dataset('panda')).plot() Parameters ---------- dataset : Dataset The starting dataset to morph into other shapes. """ def __init__(self, dataset: Dataset) -> None: y_bounds = dataset.data_bounds.y_bounds ymin, ymax = y_bounds ymid = ymax - y_bounds.range / 2 y_offset = y_bounds.range / 10 ymin += y_offset ymax -= y_offset xmin, xmax = dataset.data_bounds.x_bounds poly = np.polynomial.Polynomial.fit([ymin, ymid, ymax], [xmin, xmax, xmin], 2) super().__init__(*np.stack(poly.linspace()[::-1], axis=1)) def __str__(self) -> str: return 'left_parab'
[docs] class RightParabola(PointCollection): """ Class for the right parabola shape. .. plot:: :scale: 75 :caption: This shape is generated using the panda dataset. from data_morph.data.loader import DataLoader from data_morph.shapes.points import RightParabola _ = RightParabola(DataLoader.load_dataset('panda')).plot() Parameters ---------- dataset : Dataset The starting dataset to morph into other shapes. """ def __init__(self, dataset: Dataset) -> None: y_bounds = dataset.data_bounds.y_bounds ymin, ymax = y_bounds ymid = ymax - y_bounds.range / 2 y_offset = y_bounds.range / 10 ymin += y_offset ymax -= y_offset xmin, xmax = dataset.data_bounds.x_bounds poly = np.polynomial.Polynomial.fit([ymin, ymid, ymax], [xmax, xmin, xmax], 2) super().__init__(*np.stack(poly.linspace()[::-1], axis=1)) def __str__(self) -> str: return 'right_parab'
[docs] class UpParabola(PointCollection): """ Class for the up parabola shape. .. plot:: :scale: 75 :caption: This shape is generated using the panda dataset. from data_morph.data.loader import DataLoader from data_morph.shapes.points import UpParabola _ = UpParabola(DataLoader.load_dataset('panda')).plot() Parameters ---------- dataset : Dataset The starting dataset to morph into other shapes. """ def __init__(self, dataset: Dataset) -> None: x_bounds = dataset.data_bounds.x_bounds xmin, xmax = x_bounds xmid = xmax - x_bounds.range / 2 x_offset = x_bounds.range / 10 xmin += x_offset xmax -= x_offset ymin, ymax = dataset.data_bounds.y_bounds poly = np.polynomial.Polynomial.fit([xmin, xmid, xmax], [ymax, ymin, ymax], 2) super().__init__(*np.stack(poly.linspace(), axis=1)) def __str__(self) -> str: return 'up_parab'
[docs] class Scatter(PointCollection): """ Class for the scatter shape: a cloud of randomly-scattered points. .. plot:: :scale: 75 :caption: This shape is generated using the panda dataset. from data_morph.data.loader import DataLoader from data_morph.shapes.points import Scatter _ = Scatter(DataLoader.load_dataset('panda')).plot() Parameters ---------- dataset : Dataset The starting dataset to morph into other shapes. """ def __init__(self, dataset: Dataset) -> None: rng = np.random.default_rng(1) center = (dataset.df.x.mean(), dataset.df.y.mean()) points = [center] max_radius = max(dataset.df.x.std(), dataset.df.y.std()) for radius in np.linspace(max_radius // 5, max_radius, num=5): for angle in np.linspace(0, 360, num=50, endpoint=False): points.append( ( center[0] + np.cos(angle) * radius + rng.standard_normal() * max_radius, center[1] + np.sin(angle) * radius + rng.standard_normal() * max_radius, ) ) super().__init__(*points) self._alpha = 0.4
[docs] def distance(self, x: Number, y: Number) -> int: """ No-op that allows returns 0 so that all perturbations are accepted. Parameters ---------- x, y : int or float Coordinates of a point in 2D space. Returns ------- int Always returns 0 to allow for scattering of the points. """ return 0