diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..65fc4cc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..fc41e60 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,23 @@ +name: build + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + publish: + runs-on: ubuntu-latest + container: + image: python:latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install requirements + run: | + pip install --upgrade pip + pip install -r requirements.txt + pip install -r ./docs/requirements.txt \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0c92ac3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,23 @@ +name: release + +on: + release: + types: [created] + +jobs: + pypi: + runs-on: ubuntu-latest + container: + image: python:latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Build + run: | + pip install --no-cache-dir setuptools wheel pep517 + python -m pep517.build . + - name: Publish + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22214af --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Configureation Files +.vs +.vscode +.devcontainer +.idea + +# Working on +# /docs +codecov.yml + +# Temp Files +.DS_Store +.dropbox +.ssh +.ipynb_checkpoints +*.pyc +*.ini +__pycache__ +/build +/dist +/*.egg-info +.pytest_cache + +# Filter Directory +/result +/projects + +# test file +/test.py diff --git a/README.md b/README.md index 0871928..5bbdb52 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # alpsplot -My personal plotting library following alps-lab style. +Python plotting library of alps-lab style using matplotlib. diff --git a/alpsplot/__init__.py b/alpsplot/__init__.py new file mode 100644 index 0000000..910a01b --- /dev/null +++ b/alpsplot/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + +from .version import __version__ + +from .figure import Figure +from .colormap import * +from .utils import * diff --git a/alpsplot/colormap.py b/alpsplot/colormap.py new file mode 100644 index 0000000..73be10f --- /dev/null +++ b/alpsplot/colormap.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 + +from matplotlib.cm import get_cmap + +cmap_tab20c = get_cmap('tab20c') +tab20c_color: dict[str, list[tuple[float]]] = { + 'deep': [cmap_tab20c(0), cmap_tab20c(8), cmap_tab20c(4), cmap_tab20c(12), ], + 'light': [cmap_tab20c(1), cmap_tab20c(9), cmap_tab20c(5), cmap_tab20c(13), ], +} + +google_color = { + 'red': '#DB4437', + 'green': '#0F9D58', + 'blue': '#4285F4', + 'yellow': '#F4B400', +} +ting_color = { + 'red': '#DB3236', + 'red_carrot': '#E34638', + 'red_deep': '#C44C3F', + + 'purple': '#593982', + + 'green': '#429E46', + + 'blue': '#6876AB', + 'blue_light': '#4885ED', + 'blue_grey': '#7396AE', + + 'yellow': '#F7B52B', + 'yellow_deep': '#E7AD4C', + + 'grey': '#989898', +} +color = { + 'red': { + 'red': '#FF0000', + 'light': '#FF7F7F', + 'dark': '#8B0000', + + 'CMYK': '#ED1C24', + 'NCS': '#C40233', + 'munsell': '#F2003C', + 'pantone': '#ED2939', + 'crayola': '#EE204D', + + 'scarlet': '#FF2400', + 'imperial': '#ED2939', + 'indian': '#CD5C5C', + 'spanish': '#E60026', + 'carmine': '#960018', + 'ruby': '#E0115F', + 'crimson': '#DC143C', + 'rusty': '#DA2C43', + 'fire_engine': '#CE2029', + 'cardinal': '#C41E3A', + 'chili': '#E23D28', + 'cornell': '#B31B1B', + 'fire_brick': '#B22222', + 'madder': '#A50021', + 'redwood': '#A45A52', + 'ou_crimson': '#841617', + 'chocolate_cosmos': '#58111A', + 'maroon': '#800000', + 'barn': '#7C0A02', + 'turkey': '#A91101', + 'cinnabar': '#E44D2E', + 'vermilion': '#E34234', + 'blood': '#660000', + 'lust': '#E62020', + + + 'pink': '#FFC0CB', + 'salmon_pink': '#FF91A4', + 'coral_pink': '#F88379', + 'salmon': '#FA8072', + }, + 'pink': { + 'pink': '#FFC0CB', + 'light': '#FFB6C1', + 'bright': '#FF007F', + 'hot': '#FF69B4', + 'deep': '#FF1493', + 'light_deep': '#FF5CCD', + 'ultra': '#FF6FFF', + 'shocking': '#FC0FC0', + 'super': '#CF6BA9', + 'light_hot': '#FFB3DE', + + 'champagne': '#F1DDCF', + 'lace': '#FFDDF4', + 'piggy': '#FDDDE6', + 'pale': '#F9CCCA', + 'baby': '#F4C2C2', + 'G&S': '#F7BFBE', + 'cameo': '#EFBBCC', + 'orchid': '#F2BDCD', + 'fairy_tale': '#F2C1D1', + 'cherry_blossom': '#FFB7C5', + 'crayola': '#FBAED2', + 'cotton': '#FFBCD9', + 'carnation': '#FFA6C9', + 'baker_miller': '#FF91AF', + 'tickle_me': '#FC89AC', + 'amaranth': '#F19CBB', + 'charm': '#E68FAC', + 'china': '#DE6FA1', + 'mimi': '#FFDAE9', + 'tango': '#E4717A', + 'congo': '#F88379', + 'salmon': '#FA8072', + 'pastel': '#DEA5A4', + 'new_york': '#D7837F', + 'solid': '#893843', + 'silver': '#C4AEAD', + 'queen': '#E8CCD7', + 'lavender': '#DBB2D1', + 'mountbatten': '#997A8D', + 'chilean': '#E8C3BA', + 'pale_dogwood': '#EDCDC2', + 'pantone': '#D74894', + 'mexican': '#E4007C', + 'barbie': '#DA1884', + 'fandango': '#DE5285', + 'paradise': '#E63E62', + 'brink': '#FB607F', + 'french': '#FD6C9E', + 'persian': '#F77FBE', + 'rose': '#FF66CC', + 'rose_pumpadour': '#ED7A9B', + 'steel': '#CC33CC', + }, + 'yellow': { + 'yellow': '#F4B400', + 'light': '#FFFFE0', + + 'goldenrod': '#DAA520' + }, + 'green': { + 'green': '#00FF00', + 'html': '#008000', + 'dark_x11': '#006400', + 'light': '#90EE90', + 'lime': '#32CD32', + 'pale': '#98FB98', + 'cmyk': '#00A550', + 'ncs': '#009F6B', + 'munsell': '#00A877', + 'pantone': '#00AD83', + 'crayola': '#1CAC78', + 'bright': '#66FF00', + 'mint_bright': '#4FFFB0', + 'dark': '#013220', + 'pastel_dark': '#03C03C', + 'green_yellow': '#ADFF2F', + 'harlequin': '#3FFF00', + 'neon': '#39FF14', + 'screamin': '#76FF7A', + + 'army': '#4B5320', + 'apple': '#8DB600', + 'turtle': '#8A9A5B', + 'avocado': '#568203', + 'olive': '#808000', + 'pear': '#D1E231', + 'tea': '#D0F0C0', + 'forest': '#228B22', + }, + 'cyan': { + 'cyan': '#00FFFF', + 'cmyk': '#00B7EB', + + }, + 'blue': { + 'blue': '#0000FF', + 'cmyk': '#333399', + 'ncs': '#0087BD', + 'munsell': '#0093AF', + 'pantone': '#0018A8', + 'crayola': '#1F75FE', + + 'dark': '#00008B', + 'light': '#E0FFFF', + 'pale': '#87D3F8', + + 'baby': '#89CFF0', + 'light2': '#ADD8E6', + 'periwinkle': '#CCCCFF', + 'powder': '#B0E0E6', + 'ice': '#99FFFF', + 'morning': '#8da399', + + 'sky_blue': '#80DAEB', + + }, + + 'brown': { + 'brown': "#964B00", + 'red': '#A52A2A', + 'pale': '#987654', + 'medium': '#804000', + 'dark': '#654321', + + 'beaver': '#9F8170', + 'beige': '#F5F5DC', + 'buff': '#F0DC82', + 'burnt_umber': '#8A3324', + 'chestnut': '#954535', + 'chocolate': '#7B3F00', + 'cocoa': '#D2691E', + 'desert_sand': '#EDC9AF', + 'khaki': '#C3B091', + 'kobicha': '#6B4423', + 'peru': '#CD853F', + 'raw_umber': '#826644', + 'rosy': '#BC8F8F', + 'russet': '#80461B', + 'sandy': '#F4A460', + 'smokey_topaz': '#832A0D', + 'tan': '#D2B48C', + 'taupe': '#483C32', + 'walnut': '#5C5248', + 'wood': '#C19A6B', + }, +} diff --git a/alpsplot/figure.py b/alpsplot/figure.py new file mode 100644 index 0000000..172f996 --- /dev/null +++ b/alpsplot/figure.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 + +from .fonts import add_palatino + +import os +import numpy as np + +import matplotlib.ticker as ticker +from matplotlib import rc +from matplotlib import pyplot as plt +from matplotlib.figure import Figure +from matplotlib.axes import Axes +from matplotlib.lines import Line2D +from matplotlib.container import BarContainer +import seaborn + +add_palatino() +rc('font', family='serif', serif='Palatino', weight='bold') +rc('svg', image_inline=True, fonttype='none') +rc('pdf', fonttype=42) +rc('ps', fonttype=42) +rc('mathtext', fontset='cm') + + +# Markers +# '.' point marker +# ',' pixel marker +# 'o' circle marker +# 'v' triangle_down marker +# '^' triangle_up marker +# '<' triangle_left marker +# '>' triangle_right marker +# '1' tri_down marker +# '2' tri_up marker +# '3' tri_left marker +# '4' tri_right marker +# 's' square marker +# 'p' pentagon marker +# '*' star marker +# 'h' hexagon1 marker +# 'H' hexagon2 marker +# '+' plus marker +# 'x' x marker +# 'D' diamond marker +# 'd' thin_diamond marker +# '|' vline marker +# '_' hline marker + +# Line Styles +# '-' solid line style +# '--' dashed line style +# '-.' dash-dot line style +# ':' dotted line style + +class Figure: + def __init__(self, name: str, folder_path: str = None, fig: Figure = None, ax: Axes = None, figsize: tuple[float, float] = (5, 2.5), tex=False): + super(Figure, self).__init__() + if tex: + rc('text', usetex=True) + self.name: str = name + self.folder_path: str = folder_path + if folder_path is None: + self.folder_path = './output/' + self.fig: Figure = fig + self.ax: Axes = ax + if fig is None and ax is None: + self.fig: Figure = plt.figure(figsize=figsize) + self.ax = self.fig.add_subplot(1, 1, 1) + self.ax.spines['top'].set_visible(False) + self.ax.spines['bottom'].set_visible(True) + self.ax.spines['left'].set_visible(False) + self.ax.spines['right'].set_visible(False) + self.ax.grid(axis='y', linewidth=1) + self.ax.set_axisbelow(True) + + self.ax.set_xlim([0.0, 1.0]) + self.ax.set_ylim([0.0, 1.0]) + + def set_legend(self, *args, frameon: bool = True, edgecolor='white', framealpha=1.0, + fontsize=11, fontstyle='italic', **kwargs) -> None: + self.ax.legend(*args, frameon=frameon, edgecolor=edgecolor, + framealpha=framealpha, **kwargs) + plt.setp(self.ax.get_legend().get_texts(), + fontsize=fontsize, fontstyle=fontstyle) + + def set_axis_label(self, axis: str, text: str, fontsize: int = 12, + family='serif', font='Palatino', weight='bold', **kwargs): + getattr(self.ax, f'set_{axis}label')(text, fontsize=fontsize, + family=family, font=font, weight=weight, **kwargs) + + def set_title(self, text: str = None, fontsize: int = 16, fontweight: str = 'bold') -> None: + if text is None: + text = self.name + self.ax.set_title(text, fontsize=fontsize, fontweight=fontweight) + + def save(self, path: str = None, folder_path: str = None, ext: str = '.pdf') -> None: + if path is None: + if folder_path is None: + folder_path = self.folder_path + path = os.path.join(folder_path, f'{self.name}{ext}') + else: + folder_path = os.path.dirname(path) + if not os.path.exists(folder_path): + os.makedirs(folder_path) + self.fig.savefig(path, dpi=100, bbox_inches='tight') + + def set_axis_lim(self, axis: str, lim: list[float] = [0.0, 1.0], margin: list[float] = [0.0, 0.0], + piece: int = 10, _format: str = '%.1f', + fontsize: int = 11) -> None: + if _format == 'integer': + _format = '%d' + lim_func = getattr(self.ax, f'set_{axis}lim') + set_ticks_func = getattr(self.ax, f'set_{axis}ticks') + + def format_func(_str): + getattr(self.ax, f'{axis}axis').set_major_formatter( + ticker.FormatStrFormatter(_str)) + ticks = np.append( + np.arange(lim[0], lim[1], (lim[1] - lim[0]) / piece), lim[1]) + final_lim = [lim[0] - margin[0], lim[1] + margin[1]] + lim_func(final_lim) + set_ticks_func(ticks) + ticks = getattr(self.ax, f'get_{axis}ticks')() + set_ticklabels_func = getattr(self.ax, f'set_{axis}ticklabels') + set_ticklabels_func(ticks, fontsize=fontsize) + format_func(_format) + + def curve(self, x: np.ndarray, y: np.ndarray, color: str = 'black', linewidth: int = 2, + label: str = None, markerfacecolor: str = 'white', linestyle: str = '-', zorder: int = 1, **kwargs) -> Line2D: + # linestyle marker markeredgecolor markeredgewidth markerfacecolor markersize alpha + ax = seaborn.lineplot(x=x, y=y, ax=self.ax, color=color, linewidth=linewidth, + markerfacecolor=markerfacecolor, zorder=zorder, **kwargs) + line: Line2D = ax.get_lines()[-1] + line.set_linestyle(linestyle) + if label is not None: + self.curve_legend(label=label, color=color, + linewidth=linewidth, linestyle=linestyle, **kwargs) + return line + + def curve_legend(self, label: str = None, color: str = 'black', linewidth: int = 2, linestyle: str = '-', + markerfacecolor: str = 'white', **kwargs) -> Line2D: + # linestyle marker markeredgecolor markeredgewidth markerfacecolor markersize alpha + line, = self.ax.plot([], [], color=color, linewidth=linewidth, markeredgewidth=linewidth, markeredgecolor=color, + label=label, markerfacecolor=markerfacecolor, linestyle=linestyle, **kwargs) + return line + + def scatter(self, x: np.ndarray, y: np.ndarray, color: str = 'black', linewidth: int = 2, + label: str = None, marker: str = 'D', facecolor: str = 'white', zorder: int = 3, **kwargs): + # marker markeredgecolor markeredgewidth markerfacecolor markersize alpha + if label is not None: + self.curve_legend(label=label, color=color, + linewidth=linewidth, marker=marker, **kwargs) + return self.ax.scatter(x=x, y=y, color=color, linewidth=linewidth, marker=marker, facecolor=facecolor, zorder=zorder, **kwargs) + + def bar(self, x: np.ndarray, y: np.ndarray, color: str = 'black', width: float = 0.2, + align: str = 'edge', edgecolor: str = 'white', label: str = None, **kwargs) -> BarContainer: + # facecolor edgewidth alpha + return self.ax.bar(x, y, color=color, width=width, align=align, edgecolor=edgecolor, label=label, **kwargs) + + def bar3d(self, x: np.ndarray, y: np.ndarray, z: np.array, color: str = 'black', size: tuple[float, float] = 0.5, + label: str = None, **kwargs) -> BarContainer: + # facecolor edgewidth alpha + if isinstance(size, (float, int)): + size = [size, size] + return self.ax.bar3d(x=x, y=y, z=np.zeros_like(x), + dx=np.ones_like(x) * size[0], dy=np.ones_like(y) * size[1], dz=z, + color=color, label=label, **kwargs) + + def hist(self, x: np.ndarray, bins: list[float] = None, normed: bool = True, **kwargs): + return self.ax.hist(x, bins=bins, normed=normed, **kwargs) + + def autolabel(self, rects: BarContainer, above: bool = True, + fontsize: int = 6): + """Attach a text label above each bar in *rects*, displaying its height.""" + for rect in rects: + height = int(rect.get_height()) + offset = 3 if above else -13 + self.ax.annotate('%d' % (abs(height)), + xy=(rect.get_x() + rect.get_width() / 2, height), + xytext=(0, offset), # 3 points vertical offset + textcoords="offset points", + ha='center', va='bottom', fontsize=fontsize) diff --git a/alpsplot/fonts/__init__.py b/alpsplot/fonts/__init__.py new file mode 100644 index 0000000..6b2e351 --- /dev/null +++ b/alpsplot/fonts/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import os +import matplotlib.font_manager + + +def add_palatino() -> None: + dirname = os.path.dirname(__file__) + + for style in ['normal', 'italic', 'bold', 'bold_italic']: + file_path = os.path.normpath( + os.path.join(dirname, f'palatino_{style}.ttf')) + matplotlib.font_manager.fontManager.addfont(file_path) + ttflist: list[matplotlib.font_manager.FontEntry] = matplotlib.font_manager.fontManager.ttflist + for i, font in enumerate(ttflist): + if 'Palatino.ttc' in font.fname: + del ttflist[i] diff --git a/alpsplot/fonts/palatino_bold.ttf b/alpsplot/fonts/palatino_bold.ttf new file mode 100644 index 0000000..8c2d043 Binary files /dev/null and b/alpsplot/fonts/palatino_bold.ttf differ diff --git a/alpsplot/fonts/palatino_bold_italic.ttf b/alpsplot/fonts/palatino_bold_italic.ttf new file mode 100644 index 0000000..ebfc051 Binary files /dev/null and b/alpsplot/fonts/palatino_bold_italic.ttf differ diff --git a/alpsplot/fonts/palatino_italic.ttf b/alpsplot/fonts/palatino_italic.ttf new file mode 100644 index 0000000..1e659e4 Binary files /dev/null and b/alpsplot/fonts/palatino_italic.ttf differ diff --git a/alpsplot/fonts/palatino_normal.ttf b/alpsplot/fonts/palatino_normal.ttf new file mode 100644 index 0000000..99f309d Binary files /dev/null and b/alpsplot/fonts/palatino_normal.ttf differ diff --git a/alpsplot/utils.py b/alpsplot/utils.py new file mode 100644 index 0000000..b366fc6 --- /dev/null +++ b/alpsplot/utils.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 + +import numpy as np +import scipy.stats as stats +from scipy.interpolate import UnivariateSpline +# from scipy.optimize import curve_fit + + +def get_roc_curve(label: list[int], pred: list[int]) -> tuple[list[float], list[float]]: + total_inst = len(label) + total_pos_inst = len(np.where(label == 1)[0]) + + assert len(label) == len(pred) + # true positive rates and false positive rates + tprs, fprs, thresholds = [], [], [] + + # iterate over all positive thresholds + for threshold in np.unique(pred): + pred_pos_idx: np.ndarray = np.where(pred >= threshold)[0] + # number of predicted positive instances + pred_pos_inst = len(pred_pos_idx) + # number of true positive instances + true_pos_inst: int = np.count_nonzero(label[pred_pos_idx]) + + tpr = true_pos_inst / total_pos_inst + fpr = (pred_pos_inst - true_pos_inst) / (total_inst - total_pos_inst) + tprs.append(tpr) + fprs.append(fpr) + thresholds.append(threshold) + return fprs, tprs + + +def normalize(x: np.ndarray, _min: float = None, _max: float = None, tgt_min: float = 0.0, tgt_max: float = 1.0) -> np.ndarray: + if _min is None: + _min = x.min() + if _max is None: + _max = x.max() + x = (x - _min) / (_max - _min) * (tgt_max - tgt_min) + tgt_min + return x + + +def groups_err_bar(x: np.ndarray, y: np.ndarray) -> dict[float, np.ndarray]: + y_dict = {} + for _x in set(x): + y_dict[_x] = np.array([y[t] for t in range(len(y)) if x[t] == _x]) + return y_dict + + +def flatten_err_bar(y_dict: dict[float, np.ndarray]) -> tuple[np.ndarray, np.ndarray]: + x = [] + y = [] + for _x in y_dict.keys(): + for _y in y_dict[_x]: + x.append(_x) + y.append(_y) + return np.array(x), np.array(y) + + +def normalize_err_bar(x: np.ndarray, y: np.ndarray): + x = normalize(x) + y_dict = groups_err_bar(x, y) + y_mean = np.array([y_dict[_x].mean() + for _x in np.sort(list(y_dict.keys()))]) + y_norm = normalize(y_mean) + y_dict = adjust_err_bar(y_dict, y_norm - y_mean) + return flatten_err_bar(y_dict) + + +def avg_smooth_err_bar(x: np.ndarray, y: np.ndarray, window: int = 3): + y_dict = groups_err_bar(x, y) + y_mean = np.array([y_dict[_x].mean() + for _x in np.sort(list(y_dict.keys()))]) + y_smooth = avg_smooth(y_mean, window=window) + y_dict = adjust_err_bar(y_dict, y_smooth - y_mean) + return flatten_err_bar(y_dict) + + +def adjust_err_bar(y_dict: dict[float, np.ndarray], mean: np.ndarray = None, std: np.ndarray = None) -> dict[float, np.ndarray]: + sort_keys = np.sort(list(y_dict.keys())) + if isinstance(mean, float): + mean = mean * np.ones(len(sort_keys)) + if isinstance(std, float): + std = std * np.ones(len(sort_keys)) + for i in range(len(sort_keys)): + key = sort_keys[i] + if mean: + y_dict[key] = y_dict[key] + mean[i] + if std: + y_dict[key] = y_dict[key].mean() \ + + (y_dict[key] - y_dict[key].mean()) * std[i] + return y_dict + + +def avg_smooth(x: np.ndarray, window: int = 3) -> np.ndarray: + new_x = np.zeros_like(x) + for i in range(len(x)): + if i < window // 2: + new_x[i] = (x[0] * (window // 2 - i) + + np.sum(x[: i + (window + 1) // 2])) / window + elif i >= len(x) - (window - 1) // 2: + new_x[i] = (x[-1] * ((window + 1) // 2 - len(x) + i) + + np.sum(x[i - window // 2:])) / window + else: + new_x[i] = np.mean(x[i - window // 2: i + 1 + (window - 1) // 2]) + return new_x + + +def poly_fit(x: np.ndarray, y: np.ndarray, x_grid: np.ndarray, degree: int = 1) -> np.ndarray: + z = np.polyfit(x, y, degree) + y_grid = np.polyval(z, x_grid) + return y_grid + + +def tanh_fit(x: np.ndarray, y: np.ndarray, x_grid: np.ndarray, + degree: int = 1, mean_bias: float = 0.0, scale_multiplier: float = 1.0) -> np.ndarray: + mean = (max(y) + min(y)) / 2 + mean_bias + scale = max(abs(y - mean)) * scale_multiplier + fit_data = np.arctanh((y - mean) / scale) + z = np.polyfit(x, fit_data, degree) + y_grid = np.tanh(np.polyval(z, x_grid)) * scale + mean + return y_grid + + +def atan_fit(x: np.ndarray, y: np.ndarray, x_grid: np.ndarray, + degree: int = 1, mean_bias: float = 0.0, scale_multiplier: float = 1.0) -> np.ndarray: + mean = (max(y) + min(y)) / 2 + mean_bias + scale = max(abs(y - mean)) * scale_multiplier + fit_data = np.tan((y - mean) / scale) + z = np.polyfit(x, fit_data, degree) + y_grid = np.tanh(np.polyval(z, x_grid)) * scale + mean + return y_grid + + +def exp_fit(x: np.ndarray, y: np.ndarray, x_grid: np.ndarray, + degree: int = 1, increase: bool = True, eps: float = 0.01) -> np.ndarray: + y_max = max(y) + y_min = min(y) + if increase: + fit_data = np.log(y + eps - y_min) + else: + fit_data = np.log(y_max + eps - y) + + z = np.polyfit(x, fit_data, degree) + y_grid = np.exp(np.polyval(z, x_grid)) + if increase: + y_grid += y_min - eps + else: + y_grid = y_max + eps - y_grid + return y_grid + + +def inverse_fit(x: np.ndarray, y: np.ndarray, x_grid: np.ndarray, + degree: int = 1, y_lower_bound: float = 0.0) -> np.ndarray: + fit_data = 1 / (y - y_lower_bound) + z = np.polyfit(x, fit_data, degree) + y_grid = 1 / (np.polyval(z, x_grid)) + y_lower_bound + return y_grid + + +def monotone(x: np.ndarray, increase: bool = True) -> np.ndarray: + temp = 0.0 + y = np.copy(x) + if increase: + temp = min(x) + else: + temp = max(x) + for i in range(len(x)): + if ((increase and x[i] < temp) or (not increase and x[i] > temp)): + y[i] = temp + else: + temp = x[i] + return y + + +def gaussian_kde(x: np.ndarray, x_grid: np.ndarray) -> np.ndarray: + kde_func = stats.gaussian_kde(x) + y_grid = kde_func(x_grid) + return y_grid + + +def interp_fit(x: np.ndarray, y: np.ndarray, x_grid: np.ndarray, interp_num: int = 20) -> np.ndarray: + func = UnivariateSpline(x, y, s=interp_num) + y_grid = func(x_grid) + return y_grid diff --git a/alpsplot/version.py b/alpsplot/version.py new file mode 100644 index 0000000..a63ef4c --- /dev/null +++ b/alpsplot/version.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 + +__version__ = '1.0.0' diff --git a/examples/figure_attack_asr_alpha.py b/examples/figure_attack_asr_alpha.py new file mode 100644 index 0000000..bf6ff38 --- /dev/null +++ b/examples/figure_attack_asr_alpha.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 + +from alpsplot import * + +import argparse +import numpy as np + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-d', '--dataset', dest='dataset', default='cifar10') + args = parser.parse_args() + dataset: str = args.dataset + name = f'attack_asr_{dataset}_alpha' + fig = Figure(name) + fig.set_axis_label('x', r'Trigger Transparency ( $\alpha $)') + fig.set_axis_label('y', 'ASR (%)') + fig.set_axis_lim('x', lim=[0, 0.9], piece=9, margin=[0.05, 0.05], + _format='%.1f') + fig.set_axis_lim('y', lim=[0, 100], piece=5, margin=[0.0, 5.0], + _format='%d') + fig.set_title('') + + mark_dict = { + 'badnet': 'H', + 'trojannn': '^', + 'reflection_backdoor': 'o', + 'targeted_backdoor': 'v', + 'latent_backdoor': 's', + 'trojannet': 'p', + 'bypass_embed': 'h', + 'imc': 'D', + } + color_dict = { + 'badnet': ting_color['red_carrot'], + 'trojannn': ting_color['green'], + 'reflection_backdoor': ting_color['blue'], + 'targeted_backdoor': ting_color['yellow'], + 'latent_backdoor': ting_color['red_deep'], + 'trojannet': ting_color['purple'], + 'bypass_embed': ting_color['blue_light'], + 'imc': color['brown']['brown'], + } + attack_mapping = { + 'badnet': 'BN', + 'trojannn': 'TNN', + 'reflection_backdoor': 'RB', + 'targeted_backdoor': 'TB', + 'latent_backdoor': 'LB', + 'trojannet': 'ESB', + 'bypass_embed': 'ABE', + 'imc': 'IMC', + } + x = np.linspace(0.0, 1.0, 11) + y = { + 'cifar10': { + 'badnet': [96.078, 96.078, 96.078, 96.078, 95.146, 94.118, 90.196, 83.810, 72.381, 52.577], + 'latent_backdoor': [100.000, 100.000, 100.000, 100.000, 100.000, 100.000, 100.000, 100.000, 100.000, 98.113], + 'trojannn': [100.000, 100.000, 100.000, 100.000, 100.000, 100.000, 99.065, 97.196, 91.509, 62.617], + 'imc': [100.000, 100.000, 100.000, 100.000, 100.000, 100.000, 100.000, 100.000, 99.960, 99.220], + 'reflection_backdoor': [99.980, 99.810, 99.750, 99.430, 98.830, 97.330, 94.240, 87.400, 52.110, 10.660], + 'targeted_backdoor': [100.000, 100.000, 100.000, 100.000, 97.980, 95.146, 90.909, 79.412, 11.470, 10.680], + # 'clean_label_pgd': [74.243, 51.167, 26.156, 13.037, 12.898, 12.712, 12.630, 12.661, 12.650, 10.540], + 'trojannet': [100, 10.352, 10.352, 10.352, 10.352, 10.352, 10.352, 10.352, 10.352, 10.352], + 'bypass_embed': [95.320, 95.250, 94.370, 93.880, 93.300, 92.070, 90.460, 88.790, 74.320, 49.270], + }, + 'gtsrb': { + 'badnet': [95.469, 96.875, 95.312, 93.75, 93.75, 90.476, 88.525, 82.540, 65.634, 63.934], + 'latent_backdoor': [100, 100, 100, 100, 100, 100, 100, 100, 98.423, 91.803], + 'trojannn': [98.949, 98.761, 98.517, 98.086, 97.185, 95.777, 92.023, 79.223, 71.697, 51.952], + 'imc': [100, 100, 100, 100, 99.887, 99.662, 99.381, 98.461, 97.579, 88.645], + 'reflection_backdoor': [94.989, 90.709, 91.16, 83.54, 76.952, 67.98, 58.408, 50.282, 42.774, 3.979], + 'targeted_backdoor': [82.883, 78.866, 74.249, 61.374, 0.582, 0.582, 0.582, 0.582, 0.601, 0.601], + # 'clean_label_pgd': [59.553, 42.962, 5.912, 2.196, 1.52, 1.989, 1.314, 0.845, 0.938, 0.976], + 'trojannet': [100, 1.014, 0.938, 0.582, 0.582, 0.582, 0.582, 0.582, 0.582, 0.582], + 'bypass_embed': [88.288, 87.819, 87.481, 86.768, 85.304, 85.39, 80.424, 75.713, 68.412, 49.831], + }, + 'sample_imagenet': { + 'badnet': [90.000, 90.000, 89.800, 88.200, 86.600, 81.400, 46.800, 11.600, 11.600, 11.600], + 'latent_backdoor': [97.400, 97.200, 96.800, 96.200, 96.400, 94.600, 93.200, 20.200, 11.400, 11.000], + 'trojannn': [95.200, 94.400, 93.200, 87.800, 11.800, 11.200, 11.200, 11.000, 11.200, 11.200], + 'imc': [98.400, 98.000, 96.800, 96.200, 95.800, 96.000, 95.000, 11.600, 11.400, 11.200], + 'reflection_backdoor': [94.600, 94.000, 11.400, 11.400, 11.400, 11.200, 11.200, 11.200, 11.000, 10.800], + 'targeted_backdoor': [82.800, 63.800, 33.400, 13.000, 11.800, 11.800, 11.800, 11.800, 11.800, 11.800], + # 'clean_label_pgd': [59.553, 42.962, 5.912, 2.196, 1.52, 1.989, 1.314, 0.845, 0.938, 0.976], + 'trojannet': [100, 12.800, 12.800, 12.800, 12.600, 11.600, 11.400, 11.000, 11.000, 11.000], + 'bypass_embed': [82.600, 79.800, 78.400, 74.000, 72.400, 69.400, 46.800, 10.800, 10.600, 10.600], + }, + } + """Adjust plots for each dataset + """ + for i, (key, value) in enumerate(attack_mapping.items()): + y_list = np.array(y[dataset][key]) + x_list = np.array(x[:len(y_list)]) + x_grid = np.linspace(0.0, 0.9, 9000) + y_grid = np.linspace(0.0, 0.9, 9000) + if dataset == 'cifar10': + if key in ['imc', 'latent_backdoor', 'trojannn', 'reflection_backdoor', 'clean_label_pgd', 'trojannet']: + y_grid = interp_fit(x_list, y_list, x_grid) + if key in ['trojannet']: + y_grid += 5 + if key in ['clean_label_pgd']: + y_grid += 1 + y_grid = np.clip(y_grid, a_min=0.0, a_max=100.0) + y_grid = monotone(y_grid, increase=False) + y_grid = avg_smooth(y_grid, window=40) + elif key in ['badnet']: + y_grid = exp_fit(x_list, y_list, x_grid, + degree=2, increase=False, eps=5) + y_grid = monotone(y_grid, increase=False) + elif key in ['bypass_embed']: + y_grid = exp_fit(x_list, y_list, x_grid, + degree=2, increase=False, eps=3) + y_grid[-1000:] = poly_fit(x_list[-2:], + y_list[-2:], x_grid, degree=1)[-1000:] + y_grid = monotone(y_grid, increase=False) + y_grid[:-400] = avg_smooth(y_grid, window=400)[:-400] + y_grid[:-300] = avg_smooth(y_grid, window=300)[:-300] + y_grid[:-200] = avg_smooth(y_grid, window=200)[:-200] + y_grid[:-100] = avg_smooth(y_grid, window=100)[:-100] + y_grid[:-400] = avg_smooth(y_grid, window=700)[:-400] + y_grid[:-500] = avg_smooth(y_grid, window=800)[:-500] + elif key in ['targeted_backdoor']: + y_grid = exp_fit(x_list[:-1], y_list[:-1], + x_grid, degree=2, increase=False, eps=1) + y_grid[-1500:] = poly_fit(x_list[-2:], + y_list[-2:], x_grid, degree=1)[-1500:] + y_grid = avg_smooth(y_grid, window=300) + y_grid = avg_smooth(y_grid, window=400) + y_grid = avg_smooth(y_grid, window=500) + y_grid = avg_smooth(y_grid, window=600) + if dataset == 'sample_imagenet': + if key in ['badnet']: + y_grid = exp_fit(x_list[:7], y_list[:7], + x_grid, degree=2, increase=False) + y_grid[6000:] = atan_fit( + x_list[5:], y_list[5:], x_grid, degree=4, mean_bias=-3)[6000:] + y_grid = np.clip(y_grid, a_min=0.0, a_max=100.0) + y_grid = monotone(y_grid, increase=False) + y_grid = avg_smooth(y_grid, window=100) + y_grid = avg_smooth(y_grid, window=200) + y_grid = avg_smooth(y_grid, window=300) + elif key in ['targeted_backdoor']: + y_grid = atan_fit(x_list, y_list, x_grid, + degree=4, scale_multiplier=1.3) + y_grid = np.clip(y_grid, a_min=0.0, a_max=100.0) + y_grid = monotone(y_grid, increase=False) + y_grid = avg_smooth(y_grid, window=40) + elif key in ['bypass_embed']: + y_grid = atan_fit(x_list[:6], y_list[:6], x_grid, degree=2) + y_grid[5500:] = atan_fit( + x_list[5:], y_list[5:], x_grid, degree=4, mean_bias=-2)[5500:] + y_grid = np.clip(y_grid, a_min=0.0, a_max=100.0) + y_grid = monotone(y_grid, increase=False) + y_grid = avg_smooth(y_grid, window=100) + y_grid = avg_smooth(y_grid, window=200) + y_grid = avg_smooth(y_grid, window=300) + y_grid = avg_smooth(y_grid, window=400) + y_grid = avg_smooth(y_grid, window=500) + elif key in ['latent_backdoor']: + y_grid = poly_fit(x_list[:7], y_list[:7], x_grid, degree=1) + y_grid[6700:] = poly_fit( + x_list[7:], y_list[7:], x_grid, degree=3)[6700:] + y_grid = np.clip(y_grid, a_min=0.0, a_max=100.0) + y_grid = monotone(y_grid, increase=False) + y_grid = avg_smooth(y_grid, window=200) + y_grid = avg_smooth(y_grid, window=300) + y_grid = avg_smooth(y_grid, window=400) + y_grid = avg_smooth(y_grid, window=500) + elif key in ['imc']: + y_grid = poly_fit(x_list[:7], y_list[:7], x_grid, degree=1) + y_grid[6500:] = poly_fit( + x_list[7:], y_list[7:], x_grid, degree=1)[6500] + y_grid = np.clip(y_grid, a_min=0.0, a_max=100.0) + y_grid = monotone(y_grid, increase=False) + y_grid = avg_smooth(y_grid, window=200) + y_grid = avg_smooth(y_grid, window=300) + y_grid = avg_smooth(y_grid, window=400) + y_grid = avg_smooth(y_grid, window=500) + y_grid = avg_smooth(y_grid, window=600) + elif key in ['trojannn']: + y_grid[:4000] = interp_fit( + x_list[:6], y_list[:6], x_grid)[:4000] + y_grid[3000:] = exp_fit(x_list[3:], y_list[3:], x_grid, degree=2, + increase=True, eps=1e-7)[3000:] + y_grid = np.clip(y_grid, a_min=0.0, a_max=100.0) + y_grid = monotone(y_grid, increase=False) + y_grid = avg_smooth(y_grid, window=200) + y_grid = avg_smooth(y_grid, window=300) + y_grid = avg_smooth(y_grid, window=400) + y_grid = avg_smooth(y_grid, window=500) + y_grid = avg_smooth(y_grid, window=600) + elif key in ['reflection_backdoor']: + y_grid = poly_fit(x_list[:2], y_list[:2], x_grid, degree=1) + y_grid[1500:] = poly_fit( + x_list[2:], y_list[2:], x_grid, degree=1)[1500] + y_grid = np.clip(y_grid, a_min=0.0, a_max=100.0) + y_grid = monotone(y_grid, increase=False) + y_grid = avg_smooth(y_grid, window=200) + y_grid = avg_smooth(y_grid, window=300) + y_grid = avg_smooth(y_grid, window=400) + y_grid = avg_smooth(y_grid, window=600) + y_grid = avg_smooth(y_grid, window=800) + y_grid = avg_smooth(y_grid, window=100) + elif key in ['trojannet']: + y_grid = exp_fit(x_list, y_list, x_grid, degree=4) + y_grid = np.clip(y_grid, a_min=0.0, a_max=100.0) + y_grid = monotone(y_grid, increase=False) + y_grid = avg_smooth(y_grid, window=100) + y_grid[0] = y_list[0] + fig.curve(x_grid, y_grid, color=color_dict[key]) + fig.scatter( + x_list, y_list, color=color_dict[key], marker=mark_dict[key], label=attack_mapping[key]) + if dataset == 'cifar10': + fig.set_legend(ncol=2, columnspacing=1) + elif dataset == 'sample_imagenet': + fig.set_legend(ncol=1, labelspacing=0.15, loc='upper right') + # fig.ax.get_legend().remove() + fig.save(folder_path='./result/', ext='.pdf') diff --git a/examples/plot.py b/examples/plot.py new file mode 100644 index 0000000..c79bd20 --- /dev/null +++ b/examples/plot.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +from alpsplot import Figure, ting_color + +x = [0, 2.5, 5, 10, 20, 30, 40] +y = [93.78, 93.79, 93.30, 92.16, 90.58, 88.77, 86.13] + +if __name__ == '__main__': + fig = Figure('poison_percent_cifar10') + fig.set_axis_label('x', 'Poison Percent (%)') + fig.set_axis_label('y', 'Model Accuracy Drop (%)') + fig.set_axis_lim('x', lim=[0, 40], piece=4, margin=[1.0, 1.0], + _format='%d') + fig.set_axis_lim('y', lim=[0, 20], piece=5, margin=[1.0, 1.0], + _format='%d') + + fig.curve(x=x, y=y, color=ting_color['red']) + fig.scatter(x=x, y=y, color=ting_color['red'], + marker='H', label='resnet') + fig.set_legend() + fig.save(folder_path='./result') diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9787c3b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..04ac4d5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +numpy +scipy +matplotlib +seaborn diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..e0ecec9 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,37 @@ +[metadata] +name = alpsplot +version = attr:alpsplot.__version__ +description = Python plotting library of alps-lab style using matplotlib. +long_description = file:README.md +long_description_content_type = text/markdown +url = https://github.com/ain-soph/alpsplot +docs_url = https://ain-soph.github.io/alpsplot/ +author = Ren Pang +author_email = rbp5354@psu.edu +license = GPL-3 +classifier = + Intended Audience :: Developers + Intended Audience :: Education + Intended Audience :: Science/Research + License :: OSI Approved :: GNU General Public License v3 (GPLv3) + Topic :: Software Development :: Libraries :: Python Modules + Programming Language :: Python :: 3 +keywords = + plot + python + matplotlib +platforms = any + +[options] +zip_safe = False +# include_package_data = True +packages = find: +install_requires = + numpy + matplotlib + seaborn + scipy +python_requires = >=3 + +[options.package_data] +alpsplot = fonts/*.ttf diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..56d0869 --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 + +import setuptools +setuptools.setup()