From 76e02ff6846e39e1eace9cc1ff355646a1a42f05 Mon Sep 17 00:00:00 2001 From: GeorgWa Date: Tue, 27 Sep 2022 15:27:26 +0200 Subject: [PATCH 01/52] added loess regression module --- nbdev_nbs/statistics/regression.ipynb | 333 ++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 nbdev_nbs/statistics/regression.ipynb diff --git a/nbdev_nbs/statistics/regression.ipynb b/nbdev_nbs/statistics/regression.ipynb new file mode 100644 index 00000000..d5f95b80 --- /dev/null +++ b/nbdev_nbs/statistics/regression.ipynb @@ -0,0 +1,333 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Scikit-learn style implementation of the LOESS LOcally Estimated Scatterplot Smoothing regression." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "import numpy as np\n", + "\n", + "from sklearn.preprocessing import PolynomialFeatures\n", + "from sklearn.base import BaseEstimator, RegressorMixin\n", + "\n", + "EPSILON = 1e-6\n", + "\n", + "class LOESSRegression(BaseEstimator, RegressorMixin):\n", + "\n", + " def __init__(self, \n", + " n_kernels: int = 6, \n", + " kernel_size: float = 2., \n", + " polynomial_degree: int = 2):\n", + " \"\"\"scikit-learn estimator which implements a LOESS style local polynomial regression.\n", + " The number of basis functions or kernels can be explicitly defined which allows for faster and cheaper training and inference.\n", + " \n", + " Parameters\n", + " -----------\n", + "\n", + " n_kernels: int, default = 6\n", + " The number of local polynomial functions used to approximate the data. The location and extend of the kernels will be distributed to contain an equal number of datapoints in the training set.\n", + "\n", + " kernel_size: float, default = 2\n", + " A factor increasing the kernel size to overlap with the neighboring kernel.\n", + "\n", + " polynomial_degree: int, default = 2\n", + " Degree of the polynomial functions used for the local approximation.\n", + "\n", + " \"\"\"\n", + " self.n_kernels = n_kernels\n", + " self.kernel_size = kernel_size\n", + " self.polynomial_degree = polynomial_degree\n", + "\n", + " def get_params(self, deep: bool = True):\n", + " return super().get_params(deep)\n", + "\n", + " def set_params(self, **params):\n", + " return super().set_params(**params)\n", + "\n", + " def _more_tags(self):\n", + " return {'X_types': ['1darray']}\n", + "\n", + " def calculate_kernel_indices(self, x: np.ndarray):\n", + " \"\"\"Determine the indices of the datapoints belonging to each kernel.\n", + "\n", + " Parameters\n", + " -----------\n", + " x : numpy.ndarray, float, of shape (n_datapoints)\n", + "\n", + " Returns\n", + " --------\n", + " numpy.ndarray, int, of shape (n_kernels, 2)\n", + " \n", + " \"\"\"\n", + "\n", + " num_datapoints = len(x)\n", + " interval_size = num_datapoints // self.n_kernels\n", + "\n", + " start = np.arange(0,self.n_kernels) * interval_size\n", + " end = start + interval_size\n", + "\n", + " interval_extension = ((interval_size * self.kernel_size - interval_size) //2)\n", + "\n", + " start = start - interval_extension\n", + " start = np.maximum(0,start)\n", + "\n", + " end = end + interval_extension\n", + " end = np.minimum(num_datapoints,end)\n", + "\n", + " return np.column_stack([start,end]).astype(int)\n", + "\n", + " \n", + " def fit(self, x: np.ndarray, y: np.ndarray):\n", + " \"\"\"fit the model passed on provided training data.\n", + " \n", + " Parameters\n", + " -----------\n", + "\n", + " x: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1)\n", + " Training data. Note that only a single feature is supported at the moment.\n", + "\n", + " y: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1)\n", + " Target values.\n", + "\n", + " Returns\n", + " ---------\n", + "\n", + " self: object\n", + " Returns the fitted estimator.\n", + "\n", + " \"\"\"\n", + " \n", + " # As required by scikit-learn estimator guidelines\n", + " self.n_features_in_ = 1\n", + "\n", + " # Does not yet work with more than one input dimension\n", + " # axis-wise scaling and improved distance function need to be implemented\n", + " if len(x.shape) > 1:\n", + " if x.shape[1] > 1:\n", + " raise ValueError('Input arrays with more than one feature not yet supported. Please provide a matrix of shape (n_datapoints, 1) or (n_datapoints,)')\n", + "\n", + " # create flat version of the array for \n", + " idx_sorted = np.argsort(x.flat)\n", + " x_sorted = x.flat[idx_sorted]\n", + "\n", + " if len(x.shape) == 1:\n", + " x = x[...,np.newaxis]\n", + "\n", + " if len(y.shape) == 1:\n", + " y = y[...,np.newaxis]\n", + "\n", + " # kernel indices will only be calculated during fitting\n", + " kernel_indices = self.calculate_kernel_indices(x_sorted)\n", + "\n", + " # scale max and scale mean will then be used for calculating the weighht matrix\n", + " self.scale_mean = np.zeros((self.n_kernels))\n", + " self.scale_max = np.zeros((self.n_kernels))\n", + "\n", + " # scale mean and max are calculated and contain the scaling before applying the kernel\n", + " for i, area in enumerate(kernel_indices):\n", + " area_slice = slice(*area)\n", + " self.scale_mean[i] = x_sorted[area_slice].mean()\n", + " self.scale_max[i] = np.max(np.abs(x_sorted[area_slice] - self.scale_mean[i]))\n", + "\n", + " # from here on, the original column arrays are used\n", + " w = self.get_weight_matrix(x)\n", + "\n", + " # build design matrix\n", + " polynomial_transform = PolynomialFeatures(self.polynomial_degree)\n", + " x_design = polynomial_transform.fit_transform(x)\n", + " number_of_dimensions = len(x_design[0])\n", + "\n", + " self.beta = np.zeros((number_of_dimensions,self.n_kernels))\n", + "\n", + " for i, weights in enumerate(w.T):\n", + "\n", + " loadings = np.linalg.inv(x_design.T * weights @ x_design)@x_design.T\n", + " beta = (loadings*weights)@y\n", + " y_m = np.sum(x_design @ beta, axis=1)\n", + " self.beta[:,i] = np.ravel((loadings*weights)@y)\n", + " \n", + " return self\n", + "\n", + " def predict(self, x: np.ndarray):\n", + " \"\"\"Predict using the LOESS model.\n", + " \n", + " Parameters\n", + " -----------\n", + "\n", + " x: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1)\n", + " Feature data. Note that only a single feature is supported at the moment.\n", + " \n", + " Returns\n", + " ---------\n", + "\n", + " y: numpy.ndarray, float, of shape (n_samples,)\n", + " Target values.\n", + "\n", + " \"\"\"\n", + "\n", + " if len(x.shape) == 1:\n", + " x = x[...,np.newaxis]\n", + "\n", + " w = self.get_weight_matrix(x)\n", + " polynomial_transform = PolynomialFeatures(self.polynomial_degree)\n", + " x_design = polynomial_transform.fit_transform(x)\n", + "\n", + " return np.sum(x_design @ self.beta * w, axis=1)\n", + " \n", + "\n", + " def get_weight_matrix(self, x: np.ndarray):\n", + " \"\"\"Applies the fitted scaling parameter and the kernel to yield a weight matrix.\n", + "\n", + " The weight matrix is calculated based on the self.scale_mean and self.scale_max parameters which need to be calculated before calling this function.\n", + " They define the center and extend of the tricubic kernels. The first and last column are one-padded at the start and beginning to allow for extrapolation. \n", + "\n", + " Parameters\n", + " ----------\n", + "\n", + " x: numpy.ndarray\n", + " Numpy array of shape (n_datapoints, 1) which should be transformed to weights.\n", + "\n", + "\n", + " Returns\n", + " ----------\n", + " \n", + " numpy.ndarray\n", + " Weight matrix with the shape (n_datapoints, n_kernels).\n", + " \n", + " \"\"\"\n", + " w = np.tile(x,(1,self.n_kernels))\n", + "\n", + " w = np.abs(w - self.scale_mean)\n", + " w = w/self.scale_max\n", + " \n", + " # apply weighting kernel\n", + " w = self.tricubic(w)\n", + " \n", + " # perform epsilon padding at the start and end of the weight matrix to allow for extrapolation.\n", + "\n", + " # Does not work well yet\n", + "\n", + " # START\n", + " #idx_values = np.where(w[:,0] > 0)[0]\n", + " #min_idx, max_idx = idx_values[[0, -1]]\n", + " #w[:min_idx,0] = EPSILON\n", + "\n", + " # END\n", + " #idx_values = np.where(w[:,-1] > 0)[0]\n", + " #min_idx, max_idx = idx_values[[0, -1]]\n", + " #w[max_idx:,-1] = EPSILON\n", + "\n", + " # normalize column wise\n", + "\n", + " w = w/np.sum(w, axis=1, keepdims=True)\n", + "\n", + " return w\n", + " \n", + "\n", + " @staticmethod\n", + " def tricubic(x):\n", + " \"\"\"tricubic weight kernel\"\"\"\n", + " epsilon = EPSILON\n", + " mask = np.abs(x) <= 1\n", + " return mask * (np.power(1-np.power(np.abs(x),3),3) + epsilon)\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Unit tests" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/georgwallmann/miniconda3/envs/alphadia/lib/python3.8/site-packages/sklearn/utils/estimator_checks.py:290: SkipTestWarning: Can't test estimator LOESSRegression which requires input of type ['1darray']\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "from sklearn.utils.estimator_checks import check_estimator\n", + "check_estimator(LOESSRegression())" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "def noisy_1d(x):\n", + " y = np.sin(x)\n", + " y_err = np.random.normal(y,0.5)\n", + " return y + y_err + 0.5 * x\n", + "\n", + "x_train = np.linspace(0,15,100)\n", + "y_train = noisy_1d(x_train)\n", + "\n", + "x_test = np.linspace(0,15,200)\n", + "y_test = LOESSRegression().fit(x_train, y_train).predict(x_test)\n", + "\n", + "plt.scatter(x_train,y_train)\n", + "plt.plot(x_test,y_test,c='r')\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.13 ('alphadia')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "73529403064d3d77076e792311ea0c557580cb84bf922625b9090c035bb21740" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 3d768d9fac0039b111b9f011e6083094d6f25597 Mon Sep 17 00:00:00 2001 From: GeorgWa Date: Tue, 27 Sep 2022 15:38:00 +0200 Subject: [PATCH 02/52] add nbdev export, nbdev_prepare --- alphabase/_modidx.py | 20 +++ alphabase/statistics/__init__.py | 0 alphabase/statistics/regression.py | 236 ++++++++++++++++++++++++++ nbdev_nbs/statistics/regression.ipynb | 48 +++--- 4 files changed, 281 insertions(+), 23 deletions(-) create mode 100644 alphabase/statistics/__init__.py create mode 100644 alphabase/statistics/regression.py diff --git a/alphabase/_modidx.py b/alphabase/_modidx.py index 8fdbfbfe..cc04e3eb 100644 --- a/alphabase/_modidx.py +++ b/alphabase/_modidx.py @@ -467,6 +467,26 @@ 'alphabase/spectral_library/library_base.py'), 'alphabase.spectral_library.library_base.SpecLibBase.update_precursor_mz': ( 'spectral_library/library_base.html#speclibbase.update_precursor_mz', 'alphabase/spectral_library/library_base.py')}, + 'alphabase.statistics.regression': { 'alphabase.statistics.regression.LOESSRegression': ( 'statistics/regression.html#loessregression', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.LOESSRegression.__init__': ( 'statistics/regression.html#loessregression.__init__', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.LOESSRegression._more_tags': ( 'statistics/regression.html#loessregression._more_tags', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.LOESSRegression.calculate_kernel_indices': ( 'statistics/regression.html#loessregression.calculate_kernel_indices', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.LOESSRegression.fit': ( 'statistics/regression.html#loessregression.fit', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.LOESSRegression.get_params': ( 'statistics/regression.html#loessregression.get_params', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.LOESSRegression.get_weight_matrix': ( 'statistics/regression.html#loessregression.get_weight_matrix', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.LOESSRegression.predict': ( 'statistics/regression.html#loessregression.predict', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.LOESSRegression.set_params': ( 'statistics/regression.html#loessregression.set_params', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.LOESSRegression.tricubic': ( 'statistics/regression.html#loessregression.tricubic', + 'alphabase/statistics/regression.py')}, 'alphabase.utils': { 'alphabase.utils._flatten': ('utils.html#_flatten', 'alphabase/utils.py'), 'alphabase.utils.explode_multiple_columns': ('utils.html#explode_multiple_columns', 'alphabase/utils.py'), 'alphabase.utils.process_bar': ('utils.html#process_bar', 'alphabase/utils.py')}, diff --git a/alphabase/statistics/__init__.py b/alphabase/statistics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/alphabase/statistics/regression.py b/alphabase/statistics/regression.py new file mode 100644 index 00000000..6439b461 --- /dev/null +++ b/alphabase/statistics/regression.py @@ -0,0 +1,236 @@ +# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbdev_nbs/statistics/regression.ipynb. + +# %% auto 0 +__all__ = ['EPSILON', 'LOESSRegression'] + +# %% ../../nbdev_nbs/statistics/regression.ipynb 2 +import numpy as np + +from sklearn.preprocessing import PolynomialFeatures +from sklearn.base import BaseEstimator, RegressorMixin + +from sklearn.utils.estimator_checks import check_estimator +import matplotlib.pyplot as plt + +# %% ../../nbdev_nbs/statistics/regression.ipynb 3 +EPSILON = 1e-6 + +class LOESSRegression(BaseEstimator, RegressorMixin): + + def __init__(self, + n_kernels: int = 6, + kernel_size: float = 2., + polynomial_degree: int = 2): + """scikit-learn estimator which implements a LOESS style local polynomial regression. + The number of basis functions or kernels can be explicitly defined which allows for faster and cheaper training and inference. + + Parameters + ----------- + + n_kernels: int, default = 6 + The number of local polynomial functions used to approximate the data. The location and extend of the kernels will be distributed to contain an equal number of datapoints in the training set. + + kernel_size: float, default = 2 + A factor increasing the kernel size to overlap with the neighboring kernel. + + polynomial_degree: int, default = 2 + Degree of the polynomial functions used for the local approximation. + + """ + self.n_kernels = n_kernels + self.kernel_size = kernel_size + self.polynomial_degree = polynomial_degree + + def get_params(self, deep: bool = True): + return super().get_params(deep) + + def set_params(self, **params): + return super().set_params(**params) + + def _more_tags(self): + return {'X_types': ['1darray']} + + def calculate_kernel_indices(self, x: np.ndarray): + """Determine the indices of the datapoints belonging to each kernel. + + Parameters + ----------- + x : numpy.ndarray, float, of shape (n_datapoints) + + Returns + -------- + numpy.ndarray, int, of shape (n_kernels, 2) + + """ + + num_datapoints = len(x) + interval_size = num_datapoints // self.n_kernels + + start = np.arange(0,self.n_kernels) * interval_size + end = start + interval_size + + interval_extension = ((interval_size * self.kernel_size - interval_size) //2) + + start = start - interval_extension + start = np.maximum(0,start) + + end = end + interval_extension + end = np.minimum(num_datapoints,end) + + return np.column_stack([start,end]).astype(int) + + + def fit(self, x: np.ndarray, y: np.ndarray): + """fit the model passed on provided training data. + + Parameters + ----------- + + x: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1) + Training data. Note that only a single feature is supported at the moment. + + y: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1) + Target values. + + Returns + --------- + + self: object + Returns the fitted estimator. + + """ + + # As required by scikit-learn estimator guidelines + self.n_features_in_ = 1 + + # Does not yet work with more than one input dimension + # axis-wise scaling and improved distance function need to be implemented + if len(x.shape) > 1: + if x.shape[1] > 1: + raise ValueError('Input arrays with more than one feature not yet supported. Please provide a matrix of shape (n_datapoints, 1) or (n_datapoints,)') + + # create flat version of the array for + idx_sorted = np.argsort(x.flat) + x_sorted = x.flat[idx_sorted] + + if len(x.shape) == 1: + x = x[...,np.newaxis] + + if len(y.shape) == 1: + y = y[...,np.newaxis] + + # kernel indices will only be calculated during fitting + kernel_indices = self.calculate_kernel_indices(x_sorted) + + # scale max and scale mean will then be used for calculating the weighht matrix + self.scale_mean = np.zeros((self.n_kernels)) + self.scale_max = np.zeros((self.n_kernels)) + + # scale mean and max are calculated and contain the scaling before applying the kernel + for i, area in enumerate(kernel_indices): + area_slice = slice(*area) + self.scale_mean[i] = x_sorted[area_slice].mean() + self.scale_max[i] = np.max(np.abs(x_sorted[area_slice] - self.scale_mean[i])) + + # from here on, the original column arrays are used + w = self.get_weight_matrix(x) + + # build design matrix + polynomial_transform = PolynomialFeatures(self.polynomial_degree) + x_design = polynomial_transform.fit_transform(x) + number_of_dimensions = len(x_design[0]) + + self.beta = np.zeros((number_of_dimensions,self.n_kernels)) + + for i, weights in enumerate(w.T): + + loadings = np.linalg.inv(x_design.T * weights @ x_design)@x_design.T + beta = (loadings*weights)@y + y_m = np.sum(x_design @ beta, axis=1) + self.beta[:,i] = np.ravel((loadings*weights)@y) + + return self + + def predict(self, x: np.ndarray): + """Predict using the LOESS model. + + Parameters + ----------- + + x: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1) + Feature data. Note that only a single feature is supported at the moment. + + Returns + --------- + + y: numpy.ndarray, float, of shape (n_samples,) + Target values. + + """ + + if len(x.shape) == 1: + x = x[...,np.newaxis] + + w = self.get_weight_matrix(x) + polynomial_transform = PolynomialFeatures(self.polynomial_degree) + x_design = polynomial_transform.fit_transform(x) + + return np.sum(x_design @ self.beta * w, axis=1) + + + def get_weight_matrix(self, x: np.ndarray): + """Applies the fitted scaling parameter and the kernel to yield a weight matrix. + + The weight matrix is calculated based on the self.scale_mean and self.scale_max parameters which need to be calculated before calling this function. + They define the center and extend of the tricubic kernels. The first and last column are one-padded at the start and beginning to allow for extrapolation. + + Parameters + ---------- + + x: numpy.ndarray + Numpy array of shape (n_datapoints, 1) which should be transformed to weights. + + + Returns + ---------- + + numpy.ndarray + Weight matrix with the shape (n_datapoints, n_kernels). + + """ + w = np.tile(x,(1,self.n_kernels)) + + w = np.abs(w - self.scale_mean) + w = w/self.scale_max + + # apply weighting kernel + w = self.tricubic(w) + + # perform epsilon padding at the start and end of the weight matrix to allow for extrapolation. + + # Does not work well yet + + # START + #idx_values = np.where(w[:,0] > 0)[0] + #min_idx, max_idx = idx_values[[0, -1]] + #w[:min_idx,0] = EPSILON + + # END + #idx_values = np.where(w[:,-1] > 0)[0] + #min_idx, max_idx = idx_values[[0, -1]] + #w[max_idx:,-1] = EPSILON + + # normalize column wise + + w = w/np.sum(w, axis=1, keepdims=True) + + return w + + + @staticmethod + def tricubic(x): + """tricubic weight kernel""" + epsilon = EPSILON + mask = np.abs(x) <= 1 + return mask * (np.power(1-np.power(np.abs(x),3),3) + epsilon) + diff --git a/nbdev_nbs/statistics/regression.ipynb b/nbdev_nbs/statistics/regression.ipynb index d5f95b80..27802451 100644 --- a/nbdev_nbs/statistics/regression.ipynb +++ b/nbdev_nbs/statistics/regression.ipynb @@ -9,7 +9,16 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| default_exp statistics.regression" + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -19,6 +28,18 @@ "from sklearn.preprocessing import PolynomialFeatures\n", "from sklearn.base import BaseEstimator, RegressorMixin\n", "\n", + "from sklearn.utils.estimator_checks import check_estimator\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "\n", "EPSILON = 1e-6\n", "\n", "class LOESSRegression(BaseEstimator, RegressorMixin):\n", @@ -251,7 +272,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -264,13 +285,12 @@ } ], "source": [ - "from sklearn.utils.estimator_checks import check_estimator\n", "check_estimator(LOESSRegression())" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -285,7 +305,7 @@ } ], "source": [ - "import matplotlib.pyplot as plt\n", + "\n", "def noisy_1d(x):\n", " y = np.sin(x)\n", " y_err = np.random.normal(y,0.5)\n", @@ -308,24 +328,6 @@ "display_name": "Python 3.8.13 ('alphadia')", "language": "python", "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.13" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "73529403064d3d77076e792311ea0c557580cb84bf922625b9090c035bb21740" - } } }, "nbformat": 4, From a5ee67a128cb93c2cb1eeb922dfbc42101278c48 Mon Sep 17 00:00:00 2001 From: GeorgWa Date: Tue, 27 Sep 2022 15:52:20 +0200 Subject: [PATCH 03/52] fixed extrapolation bug --- alphabase/statistics/regression.py | 19 +++++++++---------- nbdev_nbs/statistics/regression.ipynb | 19 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/alphabase/statistics/regression.py b/alphabase/statistics/regression.py index 6439b461..42e7e375 100644 --- a/alphabase/statistics/regression.py +++ b/alphabase/statistics/regression.py @@ -206,19 +206,18 @@ def get_weight_matrix(self, x: np.ndarray): # apply weighting kernel w = self.tricubic(w) - # perform epsilon padding at the start and end of the weight matrix to allow for extrapolation. - - # Does not work well yet - + #perform epsilon padding at the start and end of the weight matrix to allow for extrapolation. # START - #idx_values = np.where(w[:,0] > 0)[0] - #min_idx, max_idx = idx_values[[0, -1]] - #w[:min_idx,0] = EPSILON + idx_values = np.where(w[:,0] > 0)[0] + if len(idx_values) > 0: + min_idx, max_idx = idx_values[[0, -1]] + w[:min_idx,0] = EPSILON # END - #idx_values = np.where(w[:,-1] > 0)[0] - #min_idx, max_idx = idx_values[[0, -1]] - #w[max_idx:,-1] = EPSILON + idx_values = np.where(w[:,-1] > 0)[0] + if len(idx_values) > 0: + min_idx, max_idx = idx_values[[0, -1]] + w[max_idx:,-1] = EPSILON # normalize column wise diff --git a/nbdev_nbs/statistics/regression.ipynb b/nbdev_nbs/statistics/regression.ipynb index 27802451..f54dbd57 100644 --- a/nbdev_nbs/statistics/regression.ipynb +++ b/nbdev_nbs/statistics/regression.ipynb @@ -233,19 +233,18 @@ " # apply weighting kernel\n", " w = self.tricubic(w)\n", " \n", - " # perform epsilon padding at the start and end of the weight matrix to allow for extrapolation.\n", - "\n", - " # Does not work well yet\n", - "\n", + " #perform epsilon padding at the start and end of the weight matrix to allow for extrapolation.\n", " # START\n", - " #idx_values = np.where(w[:,0] > 0)[0]\n", - " #min_idx, max_idx = idx_values[[0, -1]]\n", - " #w[:min_idx,0] = EPSILON\n", + " idx_values = np.where(w[:,0] > 0)[0]\n", + " if len(idx_values) > 0:\n", + " min_idx, max_idx = idx_values[[0, -1]]\n", + " w[:min_idx,0] = EPSILON\n", "\n", " # END\n", - " #idx_values = np.where(w[:,-1] > 0)[0]\n", - " #min_idx, max_idx = idx_values[[0, -1]]\n", - " #w[max_idx:,-1] = EPSILON\n", + " idx_values = np.where(w[:,-1] > 0)[0]\n", + " if len(idx_values) > 0:\n", + " min_idx, max_idx = idx_values[[0, -1]]\n", + " w[max_idx:,-1] = EPSILON\n", "\n", " # normalize column wise\n", "\n", From 77449a866c80fb296791b3cf13360716e1ed9d54 Mon Sep 17 00:00:00 2001 From: GeorgWa Date: Tue, 27 Sep 2022 17:50:25 +0200 Subject: [PATCH 04/52] updated docstrings --- alphabase/statistics/regression.py | 69 ++++----- nbdev_nbs/statistics/regression.ipynb | 200 +++++++++++++++++++++----- 2 files changed, 201 insertions(+), 68 deletions(-) diff --git a/alphabase/statistics/regression.py b/alphabase/statistics/regression.py index 42e7e375..9bb08590 100644 --- a/alphabase/statistics/regression.py +++ b/alphabase/statistics/regression.py @@ -10,33 +10,33 @@ from sklearn.base import BaseEstimator, RegressorMixin from sklearn.utils.estimator_checks import check_estimator -import matplotlib.pyplot as plt -# %% ../../nbdev_nbs/statistics/regression.ipynb 3 + +# %% ../../nbdev_nbs/statistics/regression.ipynb 4 EPSILON = 1e-6 class LOESSRegression(BaseEstimator, RegressorMixin): + """scikit-learn estimator which implements a LOESS style local polynomial regression. The number of basis functions or kernels can be explicitly defined which allows for faster and cheaper training and inference. + + Parameters + ---------- + + n_kernels : int + default = 6, The number of local polynomial functions used to approximate the data. The location and extend of the kernels will be distributed to contain an equal number of datapoints in the training set. + + kernel_size : float + default = 2, A factor increasing the kernel size to overlap with the neighboring kernel. + + polynomial_degree : int + default = 2, Degree of the polynomial functions used for the local approximation. + + """ def __init__(self, n_kernels: int = 6, kernel_size: float = 2., polynomial_degree: int = 2): - """scikit-learn estimator which implements a LOESS style local polynomial regression. - The number of basis functions or kernels can be explicitly defined which allows for faster and cheaper training and inference. - - Parameters - ----------- - - n_kernels: int, default = 6 - The number of local polynomial functions used to approximate the data. The location and extend of the kernels will be distributed to contain an equal number of datapoints in the training set. - kernel_size: float, default = 2 - A factor increasing the kernel size to overlap with the neighboring kernel. - - polynomial_degree: int, default = 2 - Degree of the polynomial functions used for the local approximation. - - """ self.n_kernels = n_kernels self.kernel_size = kernel_size self.polynomial_degree = polynomial_degree @@ -54,12 +54,14 @@ def calculate_kernel_indices(self, x: np.ndarray): """Determine the indices of the datapoints belonging to each kernel. Parameters - ----------- - x : numpy.ndarray, float, of shape (n_datapoints) + ---------- + x : numpy.ndarray + float, of shape (n_datapoints) Returns - -------- - numpy.ndarray, int, of shape (n_kernels, 2) + ------- + numpy.ndarray, int + of shape (n_kernels, 2) """ @@ -84,16 +86,16 @@ def fit(self, x: np.ndarray, y: np.ndarray): """fit the model passed on provided training data. Parameters - ----------- + ---------- - x: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1) - Training data. Note that only a single feature is supported at the moment. + x : numpy.ndarray + float, of shape (n_samples,) or (n_samples, 1), Training data. Note that only a single feature is supported at the moment. - y: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1) - Target values. + y : numpy.ndarray, float + of shape (n_samples,) or (n_samples, 1) Target values. Returns - --------- + ------- self: object Returns the fitted estimator. @@ -155,15 +157,16 @@ def predict(self, x: np.ndarray): """Predict using the LOESS model. Parameters - ----------- + ---------- - x: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1) - Feature data. Note that only a single feature is supported at the moment. + x : numpy.ndarray + float, of shape (n_samples,) or (n_samples, 1) Feature data. Note that only a single feature is supported at the moment. Returns - --------- + ------- - y: numpy.ndarray, float, of shape (n_samples,) + y : numpy.ndarray, float + of shape (n_samples,) Target values. """ @@ -192,7 +195,7 @@ def get_weight_matrix(self, x: np.ndarray): Returns - ---------- + ------- numpy.ndarray Weight matrix with the shape (n_datapoints, n_kernels). diff --git a/nbdev_nbs/statistics/regression.ipynb b/nbdev_nbs/statistics/regression.ipynb index f54dbd57..035d6fff 100644 --- a/nbdev_nbs/statistics/regression.ipynb +++ b/nbdev_nbs/statistics/regression.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Scikit-learn style implementation of the LOESS LOcally Estimated Scatterplot Smoothing regression." + "### Regression" ] }, { @@ -28,7 +28,16 @@ "from sklearn.preprocessing import PolynomialFeatures\n", "from sklearn.base import BaseEstimator, RegressorMixin\n", "\n", - "from sklearn.utils.estimator_checks import check_estimator\n", + "from sklearn.utils.estimator_checks import check_estimator\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| hide\n", "import matplotlib.pyplot as plt" ] }, @@ -43,27 +52,27 @@ "EPSILON = 1e-6\n", "\n", "class LOESSRegression(BaseEstimator, RegressorMixin):\n", + " \"\"\"scikit-learn estimator which implements a LOESS style local polynomial regression. The number of basis functions or kernels can be explicitly defined which allows for faster and cheaper training and inference.\n", + " \n", + " Parameters\n", + " ----------\n", + "\n", + " n_kernels : int \n", + " default = 6, The number of local polynomial functions used to approximate the data. The location and extend of the kernels will be distributed to contain an equal number of datapoints in the training set.\n", + "\n", + " kernel_size : float\n", + " default = 2, A factor increasing the kernel size to overlap with the neighboring kernel.\n", + "\n", + " polynomial_degree : int\n", + " default = 2, Degree of the polynomial functions used for the local approximation.\n", + "\n", + " \"\"\"\n", "\n", " def __init__(self, \n", " n_kernels: int = 6, \n", " kernel_size: float = 2., \n", " polynomial_degree: int = 2):\n", - " \"\"\"scikit-learn estimator which implements a LOESS style local polynomial regression.\n", - " The number of basis functions or kernels can be explicitly defined which allows for faster and cheaper training and inference.\n", - " \n", - " Parameters\n", - " -----------\n", - "\n", - " n_kernels: int, default = 6\n", - " The number of local polynomial functions used to approximate the data. The location and extend of the kernels will be distributed to contain an equal number of datapoints in the training set.\n", - "\n", - " kernel_size: float, default = 2\n", - " A factor increasing the kernel size to overlap with the neighboring kernel.\n", - "\n", - " polynomial_degree: int, default = 2\n", - " Degree of the polynomial functions used for the local approximation.\n", "\n", - " \"\"\"\n", " self.n_kernels = n_kernels\n", " self.kernel_size = kernel_size\n", " self.polynomial_degree = polynomial_degree\n", @@ -81,12 +90,14 @@ " \"\"\"Determine the indices of the datapoints belonging to each kernel.\n", "\n", " Parameters\n", - " -----------\n", - " x : numpy.ndarray, float, of shape (n_datapoints)\n", + " ----------\n", + " x : numpy.ndarray\n", + " float, of shape (n_datapoints)\n", "\n", " Returns\n", - " --------\n", - " numpy.ndarray, int, of shape (n_kernels, 2)\n", + " -------\n", + " numpy.ndarray, int\n", + " of shape (n_kernels, 2)\n", " \n", " \"\"\"\n", "\n", @@ -111,16 +122,16 @@ " \"\"\"fit the model passed on provided training data.\n", " \n", " Parameters\n", - " -----------\n", + " ----------\n", "\n", - " x: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1)\n", - " Training data. Note that only a single feature is supported at the moment.\n", + " x : numpy.ndarray\n", + " float, of shape (n_samples,) or (n_samples, 1), Training data. Note that only a single feature is supported at the moment.\n", "\n", - " y: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1)\n", - " Target values.\n", + " y : numpy.ndarray, float\n", + " of shape (n_samples,) or (n_samples, 1) Target values.\n", "\n", " Returns\n", - " ---------\n", + " -------\n", "\n", " self: object\n", " Returns the fitted estimator.\n", @@ -182,15 +193,16 @@ " \"\"\"Predict using the LOESS model.\n", " \n", " Parameters\n", - " -----------\n", + " ----------\n", "\n", - " x: numpy.ndarray, float, of shape (n_samples,) or (n_samples, 1)\n", - " Feature data. Note that only a single feature is supported at the moment.\n", + " x : numpy.ndarray\n", + " float, of shape (n_samples,) or (n_samples, 1) Feature data. Note that only a single feature is supported at the moment.\n", " \n", " Returns\n", - " ---------\n", + " -------\n", "\n", - " y: numpy.ndarray, float, of shape (n_samples,)\n", + " y : numpy.ndarray, float\n", + " of shape (n_samples,)\n", " Target values.\n", "\n", " \"\"\"\n", @@ -219,7 +231,7 @@ "\n", "\n", " Returns\n", - " ----------\n", + " -------\n", " \n", " numpy.ndarray\n", " Weight matrix with the shape (n_datapoints, n_kernels).\n", @@ -262,11 +274,121 @@ " " ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#|hide\n", + "from nbdev.showdoc import show_doc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/statistics/regression.py#L85){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### LOESSRegression.fit\n", + "\n", + "> LOESSRegression.fit (x:numpy.ndarray, y:numpy.ndarray)\n", + "\n", + "fit the model passed on provided training data.\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| x | ndarray | float, of shape (n_samples,) or (n_samples, 1), Training data. Note that only a single feature is supported at the moment. |\n", + "| y | ndarray | of shape (n_samples,) or (n_samples, 1) Target values. |\n", + "| **Returns** | **self: object** | **Returns the fitted estimator.** |" + ], + "text/plain": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/statistics/regression.py#L85){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### LOESSRegression.fit\n", + "\n", + "> LOESSRegression.fit (x:numpy.ndarray, y:numpy.ndarray)\n", + "\n", + "fit the model passed on provided training data.\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| x | ndarray | float, of shape (n_samples,) or (n_samples, 1), Training data. Note that only a single feature is supported at the moment. |\n", + "| y | ndarray | of shape (n_samples,) or (n_samples, 1) Target values. |\n", + "| **Returns** | **self: object** | **Returns the fitted estimator.** |" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_doc(LOESSRegression.fit)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/statistics/regression.py#L156){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### LOESSRegression.predict\n", + "\n", + "> LOESSRegression.predict (x:numpy.ndarray)\n", + "\n", + "Predict using the LOESS model.\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| x | ndarray | float, of shape (n_samples,) or (n_samples, 1) Feature data. Note that only a single feature is supported at the moment. |\n", + "| **Returns** | **numpy.ndarray, float** | |" + ], + "text/plain": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/statistics/regression.py#L156){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### LOESSRegression.predict\n", + "\n", + "> LOESSRegression.predict (x:numpy.ndarray)\n", + "\n", + "Predict using the LOESS model.\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| x | ndarray | float, of shape (n_samples,) or (n_samples, 1) Feature data. Note that only a single feature is supported at the moment. |\n", + "| **Returns** | **numpy.ndarray, float** | |" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_doc(LOESSRegression.predict)" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Unit tests" + "### Application example" ] }, { @@ -284,6 +406,7 @@ } ], "source": [ + "#| hide\n", "check_estimator(LOESSRegression())" ] }, @@ -294,7 +417,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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G+D11437e95HfqXYMl8/XOpN+ItXgcX48eOZMGECb775JuvXr2f8+PG88MILvFHOMqciIiL+4NUujMceg1OnsPe5ig0deng30yGleNXd9KqrrqJu3bq89957hffdcMMNxMfHM2XKFI9eQ91NRUQkUNxW3vz+e+jVC2JiIDOT9JOJDJmSAVBsu2oodSH1F790N+3WrRvz589n48aNAPz6668sWbKEK664omKjFRER8QPHLoxr2p1Zuiup1QqPPGLcvv9+aNEioupN+ItX201HjhxJTk4OLVu2JDo6GqvVynPPPceAAQNcPic/P5/8/PzCn3Nycso/WhEREV957z1YuxaqV4fRowvvjpR6E/7iVWAxffp0pk6dyscff0zr1q1Zs2YNw4cPp169egwaNMjpc8aNG8eYMWN8MlgRERGfyMmBUaOM26NHQ43ieRORUG/CX7zKsWjQoAEjR45k6NChhfeNHTuWKVOm8Mcffzh9jrMZiwYNGijHQkREzDNyJIwfD82bQ2am0RpdyuRpjoVXMxbHjx8nKqp4WkZ0dDQ2m83lc2JjY4mNLXvrjoiISMBs3QqvvGLcfuklBRU+5lVg0bdvX5577jkaNmxI69atWb16NS+//DJ33XWXv8YnIiLiW08/DQUFcOml0KeP2aMJO14theTm5jJq1ChmzZrFvn37qFevHv379+epp56icuXKHr2GtpuKiIhp1q+H1FSw2WDlSujY0ewRhQxPz99eBRa+oMBCRERM068fTJ8O114Ls2aZPZqQ4pc6FiIiIiHr11+NoMJigWeeMXs0YUuBhYiIRIannjK+9+sHbdqYO5YwpsBCRETC3/LlMGcOREUZyZviNwosREQk/DmKYd1+O7RoYe5YwpwCCxERCW+LFsG8eUa9iiKlu8U/FFiIiEh4c+RW/OMf0KiRqUOJBF4VyBIREQlWTluk/7QEFi+GypXhX/8ye4gRQYGFiIiEvPTMPYz5ch17svMK70tJimPO189TG+DOO+HMM00bXyRRYCEiIiEtPXMPQ6ZkULLaY42Nv1N7yQLsUVFYHn/clLFFIuVYiIhIyLLa7Iz5cl2poAJgyLIZAHzX5iKsjc8O7MAimGYsREQkZK3YeqjY8odD40O7uPKPJQC8fN71JG49RNcmNZ3nYURZAj3ssKbAQkREQta+3NJBBcB9y2YQhZ15TdPYULsR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mDQi/3r88ccZOXIkt9xyC23atOG2227j4YcfZty4cWYPze8cf58i7W8XnA4qtm/fzrx588J2tuLHH39k3759NGzYsPDv1/bt23n00Udp1KiR2cPzm5APLCpXrsx5553H/PnzC++z2WzMnz+frl27mjgy/7Db7QwbNoxZs2axYMECGjdubPaQ/OrSSy9l7dq1rFmzpvCrY8eODBgwgDVr1hAdHW32EH2qe/fupbYPb9y4kbPOOsukEfnX8ePHiYoq/mcoOjoam81m0ogCp3HjxiQnJxf725WTk8Py5cvD8m+XgyOo2LRpE99//z01a9Y0e0h+c9ttt/Hbb78V+/tVr149Hn/8cb799luzh+c3YbEU8sgjjzBo0CA6duxIWloar776KseOHePOO+80e2g+N3ToUD7++GNmz55NQkJC4VpsUlIS8fHxJo/O9xISEkrlj1StWpWaNWuGZV7Jww8/TLdu3Xj++ee5+eabWbFiBRMnTmTixIlmD80v+vbty3PPPUfDhg1p3bo1q1ev5uWXX+auu+4ye2g+cfToUTZv3lz489atW1mzZg01atSgYcOGDB8+nLFjx9KsWTMaN27MqFGjqFevHtdee615g66gsj5zSkoKN954IxkZGXz11VdYrdbCv2E1atSgcuXKZg273Nz9jksGTpUqVSI5OZkWLVoEeqiBY/a2FF9544037A0bNrRXrlzZnpaWZl+2bJnZQ/ILwOnX5MmTzR5awITzdlO73W7/8ssv7ampqfbY2Fh7y5Yt7RMnTjR7SH6Tk5Njf+ihh+wNGza0x8XF2c8++2z7v/71L3t+fr7ZQ/OJhQsXOv3/ddCgQXa73dhyOmrUKHvdunXtsbGx9ksvvdS+YcMGcwddQWV95q1bt7r8G7Zw4UKzh14u7n7HJUXCdlO1TRcRERGfCfkcCxEREQkeCixERETEZxRYiIiIiM8osBARERGfUWAhIiIiPqPAQkRERHxGgYWIiIj4jAILERER8RkFFiIiIuIzCixERETEZxRYiIiIiM8osBARERGf+X9FLI1Hlk4dXQAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -320,6 +443,13 @@ "plt.plot(x_test,y_test,c='r')\n", "plt.show()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From 157aaa3b7e975bcfe5d5650f1da50c24c4823fb9 Mon Sep 17 00:00:00 2001 From: GeorgWa Date: Wed, 28 Sep 2022 23:33:13 +0200 Subject: [PATCH 05/52] close #42 improved edge case handling and testing --- alphabase/statistics/regression.py | 38 +++++++++-- nbdev_nbs/statistics/regression.ipynb | 91 ++++++++++++++++++++++++--- 2 files changed, 115 insertions(+), 14 deletions(-) diff --git a/alphabase/statistics/regression.py b/alphabase/statistics/regression.py index 9bb08590..ad457386 100644 --- a/alphabase/statistics/regression.py +++ b/alphabase/statistics/regression.py @@ -105,25 +105,50 @@ def fit(self, x: np.ndarray, y: np.ndarray): # As required by scikit-learn estimator guidelines self.n_features_in_ = 1 + # === start === sanity checks === # Does not yet work with more than one input dimension # axis-wise scaling and improved distance function need to be implemented if len(x.shape) > 1: if x.shape[1] > 1: raise ValueError('Input arrays with more than one feature not yet supported. Please provide a matrix of shape (n_datapoints, 1) or (n_datapoints,)') - # create flat version of the array for - idx_sorted = np.argsort(x.flat) - x_sorted = x.flat[idx_sorted] + # at least two datapoints required + if len(x.flat) < 2: + raise ValueError('At least two datapoints required for fitting.') + + # sanity check for number of datapoints, reduce n_kernels if needed + degrees_freedom = (1 + self.polynomial_degree) * self.n_kernels + + if len(x.flat) < degrees_freedom: + print(f"Curve fitting with {self.n_kernels} kernels and polynomials of {self.polynomial_degree} degree requires at least {degrees_freedom} datapoints.") + + self.n_kernels = np.max([len(x.flat) // (1 + self.polynomial_degree),1]) + + print(f"Number of kernels will be reduced to {self.n_kernels} kernels.") + # sanity check for number of datapoints, reduce degree of polynomial if necessary + degrees_freedom = (1 + self.polynomial_degree) * self.n_kernels + if len(x.flat) < degrees_freedom: + self.polynomial_degree = len(x.flat) - 1 + + print(f"Polynomial degree will be reduced to {self.polynomial_degree}.") + + # reshape both arrays to column arrays if len(x.shape) == 1: x = x[...,np.newaxis] if len(y.shape) == 1: y = y[...,np.newaxis] + + # === end === sanity checks === + + # create flat version of the array for + idx_sorted = np.argsort(x.flat) + x_sorted = x.flat[idx_sorted] # kernel indices will only be calculated during fitting kernel_indices = self.calculate_kernel_indices(x_sorted) - + # scale max and scale mean will then be used for calculating the weighht matrix self.scale_mean = np.zeros((self.n_kernels)) self.scale_max = np.zeros((self.n_kernels)) @@ -137,11 +162,13 @@ def fit(self, x: np.ndarray, y: np.ndarray): # from here on, the original column arrays are used w = self.get_weight_matrix(x) + # build design matrix polynomial_transform = PolynomialFeatures(self.polynomial_degree) x_design = polynomial_transform.fit_transform(x) number_of_dimensions = len(x_design[0]) + self.beta = np.zeros((number_of_dimensions,self.n_kernels)) for i, weights in enumerate(w.T): @@ -150,7 +177,8 @@ def fit(self, x: np.ndarray, y: np.ndarray): beta = (loadings*weights)@y y_m = np.sum(x_design @ beta, axis=1) self.beta[:,i] = np.ravel((loadings*weights)@y) - + + return self def predict(self, x: np.ndarray): diff --git a/nbdev_nbs/statistics/regression.ipynb b/nbdev_nbs/statistics/regression.ipynb index 035d6fff..fbcdcc99 100644 --- a/nbdev_nbs/statistics/regression.ipynb +++ b/nbdev_nbs/statistics/regression.ipynb @@ -141,25 +141,50 @@ " # As required by scikit-learn estimator guidelines\n", " self.n_features_in_ = 1\n", "\n", + " # === start === sanity checks ===\n", " # Does not yet work with more than one input dimension\n", " # axis-wise scaling and improved distance function need to be implemented\n", " if len(x.shape) > 1:\n", " if x.shape[1] > 1:\n", " raise ValueError('Input arrays with more than one feature not yet supported. Please provide a matrix of shape (n_datapoints, 1) or (n_datapoints,)')\n", "\n", - " # create flat version of the array for \n", - " idx_sorted = np.argsort(x.flat)\n", - " x_sorted = x.flat[idx_sorted]\n", + " # at least two datapoints required\n", + " if len(x.flat) < 2:\n", + " raise ValueError('At least two datapoints required for fitting.')\n", + "\n", + " # sanity check for number of datapoints, reduce n_kernels if needed\n", + " degrees_freedom = (1 + self.polynomial_degree) * self.n_kernels\n", + "\n", + " if len(x.flat) < degrees_freedom:\n", + " print(f\"Curve fitting with {self.n_kernels} kernels and polynomials of {self.polynomial_degree} degree requires at least {degrees_freedom} datapoints.\")\n", + " \n", + " self.n_kernels = np.max([len(x.flat) // (1 + self.polynomial_degree),1])\n", + " \n", + " print(f\"Number of kernels will be reduced to {self.n_kernels} kernels.\")\n", + "\n", + " # sanity check for number of datapoints, reduce degree of polynomial if necessary\n", + " degrees_freedom = (1 + self.polynomial_degree) * self.n_kernels\n", + " if len(x.flat) < degrees_freedom:\n", + " self.polynomial_degree = len(x.flat) - 1\n", + "\n", + " print(f\"Polynomial degree will be reduced to {self.polynomial_degree}.\")\n", "\n", + " # reshape both arrays to column arrays\n", " if len(x.shape) == 1:\n", " x = x[...,np.newaxis]\n", "\n", " if len(y.shape) == 1:\n", " y = y[...,np.newaxis]\n", + " \n", + " # === end === sanity checks ===\n", + "\n", + " # create flat version of the array for \n", + " idx_sorted = np.argsort(x.flat)\n", + " x_sorted = x.flat[idx_sorted]\n", "\n", " # kernel indices will only be calculated during fitting\n", " kernel_indices = self.calculate_kernel_indices(x_sorted)\n", - "\n", + " \n", " # scale max and scale mean will then be used for calculating the weighht matrix\n", " self.scale_mean = np.zeros((self.n_kernels))\n", " self.scale_max = np.zeros((self.n_kernels))\n", @@ -173,11 +198,13 @@ " # from here on, the original column arrays are used\n", " w = self.get_weight_matrix(x)\n", "\n", + "\n", " # build design matrix\n", " polynomial_transform = PolynomialFeatures(self.polynomial_degree)\n", " x_design = polynomial_transform.fit_transform(x)\n", " number_of_dimensions = len(x_design[0])\n", "\n", + " \n", " self.beta = np.zeros((number_of_dimensions,self.n_kernels))\n", "\n", " for i, weights in enumerate(w.T):\n", @@ -186,7 +213,8 @@ " beta = (loadings*weights)@y\n", " y_m = np.sum(x_design @ beta, axis=1)\n", " self.beta[:,i] = np.ravel((loadings*weights)@y)\n", - " \n", + " \n", + "\n", " return self\n", "\n", " def predict(self, x: np.ndarray):\n", @@ -417,7 +445,7 @@ "outputs": [ { "data": { - "image/png": "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G+D11437e95HfqXYMl8/XOpN+ItXgcX48eOZMGECb775JuvXr2f8+PG88MILvFHOMqciIiL+4NUujMceg1OnsPe5ig0deng30yGleNXd9KqrrqJu3bq89957hffdcMMNxMfHM2XKFI9eQ91NRUQkUNxW3vz+e+jVC2JiIDOT9JOJDJmSAVBsu2oodSH1F790N+3WrRvz589n48aNAPz6668sWbKEK664omKjFRER8QPHLoxr2p1Zuiup1QqPPGLcvv9+aNEioupN+ItX201HjhxJTk4OLVu2JDo6GqvVynPPPceAAQNcPic/P5/8/PzCn3Nycso/WhEREV957z1YuxaqV4fRowvvjpR6E/7iVWAxffp0pk6dyscff0zr1q1Zs2YNw4cPp169egwaNMjpc8aNG8eYMWN8MlgRERGfyMmBUaOM26NHQ43ieRORUG/CX7zKsWjQoAEjR45k6NChhfeNHTuWKVOm8Mcffzh9jrMZiwYNGijHQkREzDNyJIwfD82bQ2am0RpdyuRpjoVXMxbHjx8nKqp4WkZ0dDQ2m83lc2JjY4mNLXvrjoiISMBs3QqvvGLcfuklBRU+5lVg0bdvX5577jkaNmxI69atWb16NS+//DJ33XWXv8YnIiLiW08/DQUFcOml0KeP2aMJO14theTm5jJq1ChmzZrFvn37qFevHv379+epp56icuXKHr2GtpuKiIhp1q+H1FSw2WDlSujY0ewRhQxPz99eBRa+oMBCRERM068fTJ8O114Ls2aZPZqQ4pc6FiIiIiHr11+NoMJigWeeMXs0YUuBhYiIRIannjK+9+sHbdqYO5YwpsBCRETC3/LlMGcOREUZyZviNwosREQk/DmKYd1+O7RoYe5YwpwCCxERCW+LFsG8eUa9iiKlu8U/FFiIiEh4c+RW/OMf0KiRqUOJBF4VyBIREQlWTluk/7QEFi+GypXhX/8ye4gRQYGFiIiEvPTMPYz5ch17svMK70tJimPO189TG+DOO+HMM00bXyRRYCEiIiEtPXMPQ6ZkULLaY42Nv1N7yQLsUVFYHn/clLFFIuVYiIhIyLLa7Iz5cl2poAJgyLIZAHzX5iKsjc8O7MAimGYsREQkZK3YeqjY8odD40O7uPKPJQC8fN71JG49RNcmNZ3nYURZAj3ssKbAQkREQta+3NJBBcB9y2YQhZ15TdPYULsR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mDQi/3r88ccZOXIkt9xyC23atOG2227j4YcfZty4cWYPze8cf58i7W8XnA4qtm/fzrx588J2tuLHH39k3759NGzYsPDv1/bt23n00Udp1KiR2cPzm5APLCpXrsx5553H/PnzC++z2WzMnz+frl27mjgy/7Db7QwbNoxZs2axYMECGjdubPaQ/OrSSy9l7dq1rFmzpvCrY8eODBgwgDVr1hAdHW32EH2qe/fupbYPb9y4kbPOOsukEfnX8ePHiYoq/mcoOjoam81m0ogCp3HjxiQnJxf725WTk8Py5cvD8m+XgyOo2LRpE99//z01a9Y0e0h+c9ttt/Hbb78V+/tVr149Hn/8cb799luzh+c3YbEU8sgjjzBo0CA6duxIWloar776KseOHePOO+80e2g+N3ToUD7++GNmz55NQkJC4VpsUlIS8fHxJo/O9xISEkrlj1StWpWaNWuGZV7Jww8/TLdu3Xj++ee5+eabWbFiBRMnTmTixIlmD80v+vbty3PPPUfDhg1p3bo1q1ev5uWXX+auu+4ye2g+cfToUTZv3lz489atW1mzZg01atSgYcOGDB8+nLFjx9KsWTMaN27MqFGjqFevHtdee615g66gsj5zSkoKN954IxkZGXz11VdYrdbCv2E1atSgcuXKZg273Nz9jksGTpUqVSI5OZkWLVoEeqiBY/a2FF9544037A0bNrRXrlzZnpaWZl+2bJnZQ/ILwOnX5MmTzR5awITzdlO73W7/8ssv7ampqfbY2Fh7y5Yt7RMnTjR7SH6Tk5Njf+ihh+wNGza0x8XF2c8++2z7v/71L3t+fr7ZQ/OJhQsXOv3/ddCgQXa73dhyOmrUKHvdunXtsbGx9ksvvdS+YcMGcwddQWV95q1bt7r8G7Zw4UKzh14u7n7HJUXCdlO1TRcRERGfCfkcCxEREQkeCixERETEZxRYiIiIiM8osBARERGfUWAhIiIiPqPAQkRERHxGgYWIiIj4jAILERER8RkFFiIiIuIzCixERETEZxRYiIiIiM8osBARERGf+X9FLI1Hlk4dXQAAAABJRU5ErkJggg==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh8AAAGdCAYAAACyzRGfAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAABwdElEQVR4nO3dd3iTZffA8W+STkoHZbVIgcqGsmcFERBkDzcI6g8VFUEZvoqoiIqCuACFFwQVUWS42FJe9pJdC5QNsmnZtFiglOT5/RFSmjbjSZvV5nyuq5c0efLkTlr7nNz3OefWKIqiIIQQQgjhJlpPD0AIIYQQvkWCDyGEEEK4lQQfQgghhHArCT6EEEII4VYSfAghhBDCrST4EEIIIYRbSfAhhBBCCLeS4EMIIYQQbuXn6QHkZjAYOHv2LKGhoWg0Gk8PRwghhBAqKIrCtWvXKFeuHFqt7bkNrws+zp49S0xMjKeHIYQQQoh8OHXqFOXLl7d5jNcFH6GhoYBx8GFhYR4ejRBCCCHUSE9PJyYmJvs6bovXBR+mpZawsDAJPoQQQohCRk3KhCScCiGEEMKtJPgQQgghhFtJ8CGEEEIIt5LgQwghhBBuJcGHEEIIIdxKgg8hhBBCuJUEH0IIIYRwKwk+hBBCCOFWXtdkTAghhCiq9AaFbccuc/7aTcqEBtE0NhKd1vf2MZPgQwghhHCDhOQUPli8j5S0m9m3RYcHMapbLTrGRXtwZO7n8LLL+vXr6datG+XKlUOj0bBgwQKz+xVF4b333iM6Oprg4GDatWvH4cOHnTVeIYQQotBJSE5hwKxEs8ADIDXtJgNmJZKQnOKhkXmGw8FHRkYG9erVY/LkyRbv//TTT/nqq6+YOnUqW7duJSQkhA4dOnDz5k2LxwshhBBFmd6g8MHifSgW7jPd9sHifegNlo4omhxedunUqROdOnWyeJ+iKEyYMIF3332XHj16APDjjz9StmxZFixYQK9evQo2WiGEEKKQ2Xbscp4Zj5wUICXtJtuOXSa+ckn3DcyDnFrtcuzYMVJTU2nXrl32beHh4TRr1ozNmzc786mEEEKIQuH8NXUz/2qPKwqcmnCampoKQNmyZc1uL1u2bPZ9uWVmZpKZmZn9fXp6ujOHJIQQQnhUmdAgpx5XFHi8z8fYsWMJDw/P/oqJifH0kIQQQginaRobSXR4ELYKaiND/ElNv8nmo5d8IvfDqcFHVFQUAOfOnTO7/dy5c9n35TZixAjS0tKyv06dOuXMIQkhhBAepdNqGNWtFoDVAORyRhZD5yXRe/oWWo5bXeSrX5wafMTGxhIVFcWqVauyb0tPT2fr1q3Ex8dbfExgYCBhYWFmX0IIIURR0jEumil9GxIVbn9pxRfKbx3O+fj33385cuRI9vfHjh0jKSmJyMhIKlSowJAhQ/joo4+oWrUqsbGxjBw5knLlytGzZ09njlsIIYQoVDrGRdO+VhTbjl0mNe0Go5fu53LGrTzHKRhnSD5YvI/2taKKZAdUh4OPHTt20KZNm+zvhw0bBsCzzz7LDz/8wJtvvklGRgYvvvgiV69epWXLliQkJBAU5DuJNEIIIYQlOq2G+Mol2Xz0ksXAw8Ra+W1Rac/ucPDRunVrFMV6MoxGo+HDDz/kww8/LNDAhBBCiKIqP+W3Rak9u8erXYQQQghf42j5bVFrzy7BhxBCCOFm9spvNRhnNZrGRhbJ9uwSfAghhBBuZqv81vT9qG610Gk1DrVnLywk+BBCCCE8wFr5bVR4EFP6NszO4yiK7dmd2l5dCCGEEOrlLL+1VsFSFNuzS/AhhBBCeJCp/NYaU35IatpNi3kfGoyzJU1jI102RmeTZRchhBDCizmSH1JYSPAhhBBCeDm1+SGFhSy7CCGEEIWAmvyQwkKCDyGEEKKQsJcfYo23tWWX4EMIIYQowryxLbvkfAghhBBFlLe2ZZfgQwghhCiCvLktuwQfQgghRCGlNyhsPnqJhUln2Hz0klkg4c1t2SXnQwghhCiE7OVyeHNbdpn5EEIIIQoZNbkc3tyWXYIPIYQQohBRm8vRqGIJosOD8nRFNdFgnCnxRFt2CT6EEEKIQkRtLsfOE1estmU3HTeyS02P9PuQnA8hhBDCzQrS9MtajobWoOfey2eIvXKWCldTKXkhgfjiWtalXGHT2eucV/xJDS3F6fAynA4vy7HIexi9dD9arcbt/T4k+BBCCCHcqKBNv45fzDD+Q1GofPk07Y5spdWxROqlHKb4rRt5jq9w5yunlOIliR84MztHxN37w0jwIYQQQriJKVE0d76G2iAgITmFGYt30m/vGp7YvYKaF46b3Z/hH8SRkjFcLBVN6wfqkmLw55pBw5a9Z/DLuEZ0+gXKp53nXKixRbuCcUnmg8X7aF8rym1LMBJ8CCGEEG5gL1HUXhCgP36C9BeHsHn7MoJvZwKQqfNjS4W6rKzSlG0xcRwuGYOi1fFiq1je3ZVyd3alhfVx5ez3kZ99Y/JDgg8hhBDCDdQmio5fcYgWVUrRqGIJdp64wpVTKTT44WvK/vw9T9y+DcDeMvcyp14HFtV6gPSg4mbn6Vo3mmnrj1kMcmxxZ78PCT6EEEIIN1B7cZ+05giT1hxBh4HefyfwxrqZhGca8zw2VazL5OZP8FfFeqCxvESy8fBFhwMPcG+/Dwk+hBBCCCeyVsniyMU99vIZxi2bSNPT+wDYX7oSH7V9gU2V6tt97NUbWQ6NVwNEubnfhwQfQgghhEr2SmStVbKM7FKT8OAAIoL9bQcHisITu1fw/qpvKJaVSYZ/EJ8+8Cw/NeiMQauzOTYNEG7v/BYeAzCqWy239vuQ4EMIIYRQwV6JrLVKlpS0m7wy+2+75y926wafJHxN9/3rAdhYsR7DOw3mTHgZu481hQ39WlRi/MrDal8SUQ6U+DqTBB9CCCGEHfZKZCc/1YDRS/fnK9cCoHzaOab/PpqaF46TpdXxxf1P802zR1A0lhuR555BMQUR7WtFMXf7KVLTblodS2SIPyO71iYqzLHmZs4kwYcQQghhg5oS2XcXJnM5w7FcC4CQAB3V/0lm+h+jKXkjnQshEbzc8212lq9l83GTn2qIVquxuPwzqlstBsxKRANmYzaFGGMeruP2mY7cJPgQQgghbFBTIpufwAMgfu9fTFo0jqDbt9gdVYWXHn6HlLDSVo83JYc2r1zS6oxF+1pRDGlXjRmbjlmcHfF04AESfAghhBA2uar/xeO7V/BJwtfoFAMrKzdhUI/h3PS3XRGjYDs51FJeSkSwP/1aVGJQ26oeWWKxRHa1FUIIIWxQWyIbGRJgdfv63HolJfDZsonoFAPnH3uKLZ9Ptxt4ADxQrRThwQHoDXkXgUx5KblnadJuZDFh5WFW7EtVOTrXk+BDCCGEsKFpbCTR4UFWAwsNxqqXj3rEZX9vy1NJy/hk+SQA5sU/TMm5P/FgnfKqxrLu0EV6T99Cy3GrSUhOyb7dXl4KGFu3WwpaPEGCDyGEEMIGnVbDqG7GBNDcgUXOPhmd60YzpW9DosKtz2D03LuGMcsnA/Bt4x6EfzMJnU5rN8DJzVRlYwpA1LZu33bssspncC3J+RBCCCHs6BhnDCxy51PkTuLsGBdN+1pRZo3IrmTcYvTSfdTesY7Pl44H4Jf4npT/ZjId65QD7gY4lqpULMm9EZ3avBR37t9iiwQfQgghhAqWAgtLfTJ0Wk2e3WE7pB1F8/6naBUDF3o+waO/zkbnZ96x1FqAY03O2Qy1eSnu3L/FFgk+hBBCCJUsBRZ2HTmC7uGecCsTunWj9C+zwM9yq/ScAc6y5BR+3HzC7unPX7uJv1aDVgPWUjo8sX+LLRJ8CCGEEAVgc7+XS5egc2fjfxs3hjlzwN/f5vlyBjhqgo/jF68zYeUhu0s17t6/xRYJPoQQQoh8srnfS43S8MQTcPgwVKwIixdDSIjqc5uSUK21StcAZcMCmbPtpM3AQ6uBSb0bekVzMROpdhFCCCHywVpfDVMlyrHnB8Hq1caAY8kSiIpy6Pxqqmx6N61Aarrt/BCDAiVCAhx6bleT4EMIIYRwkL2+Gj32riH2x2+MN/z4I8TF5et5TEmouct3o8KDmNK3IZVKqZtJ8ZYqFxNZdhFCCCEcZKuvRpWLJxlzp4nY6VeGUf6RRwr0XLaqbDYfvaTqHN5S5WIiwYcQQgjhIGszCcG3bvLfBZ9QLCuTDRXrc/m5wajrXWqbtSobNXkh3lTlYiLLLkIIIYSDrM0kfLhiKtUuneRc8UiGdPsPZSLUJ5jmh9ruq95S5WIiwYcQQgjhoEYVSxCZK4mz6/71PJ68Er1Gy+Bu/yGgXJRbZhzs5YV4U5WLiSy7CCGEEA4wlddezriVfVt0+gU+vrNny+T4J9haoS5T3DjjoLb7qreQ4EMIIYRQyVRemzO/QmvQ8+XSLwnPzCApuhq/dPo/pvSs6/YZh3x1X/UQCT6EEEIIFayV1/7fziXEn9xDhn8Qo554i9VvtSfAT7IabJF3RwghhFDBUnltxStneWP9jwB83PZ5dgWVYeeJK9n36w0Km49eYmHSGTYfvYTe2uYrPkZmPoQQQgg79AaFTUcumt2mUQx8+udEgm9nsrFiPWbX6wjcLcO12XrdC5NA3UlmPoQQQggbEpJTaDluNZPWHDG7/ZnEpTQ7vZcM/yDe6vQaaIzJnWVCg+y2Xk9ITnHb+L2RBB9CCCGEFdaCiJirqQxf9wMAY1v343R4WTQYZzYaVSxhs/U6wAeL9/n0EozTgw+9Xs/IkSOJjY0lODiYypUrM3r0aBTFd99kIYQQhY+1BFONYuDTZRMplpXJ5gp1+LlBJ7OGXjtPXLHaeh2MAUhK2k22HbvsqqF7PafnfIwbN44pU6Ywc+ZMateuzY4dO+jXrx/h4eG89tprzn46IYQQwiWs7d/SJymB+JN7uO4fyPCOr6FotGa5HAuTzqg6v7dt9uZOTg8+/vrrL3r06EGXLl0AqFSpEnPmzGHbtm3OfiohhBDCZSwFB2WuXWL42hkAfNrqWU6WiGZQm8oMbV89u6GX2k3cvG2zN3dy+rLLfffdx6pVqzh06BAAu3btYuPGjXTq1Mni8ZmZmaSnp5t9CSGEEJ5mKTh4b/W3hN66QVJ0NX5saPyQ3aJKabNOoqbN3qz1FjXlhnjbZm/u5PTg46233qJXr17UqFEDf39/GjRowJAhQ+jTp4/F48eOHUt4eHj2V0xMjLOHJIQQQjgsdxDxwD876XpgA3qNlrc7DELR6iwGEYV1szd3cnrw8csvv/Dzzz8ze/ZsEhMTmTlzJp9//jkzZ860ePyIESNIS0vL/jp16pSzhySEEEI4LGcQEZSVyYcrpgAwo1E39pe9F7AeRBTGzd7cSaM4uQwlJiaGt956i4EDB2bf9tFHHzFr1iwOHDhg9/Hp6emEh4eTlpZGWFiYM4cmhBBCOCwhOYWU196g35qfSSleknYvTCGsTKSqZmF6g1JoNnsrKEeu305POL1+/TparfmEik6nw2AwOPuphBBCCJfr6JeGsvEXAE69P5Zve7ZVHUQUps3e3MnpwUe3bt34+OOPqVChArVr1+bvv//myy+/5LnnnnP2UwkhhBCupSgwYACarCzo0oWmw17I7mQq8s/pwcfXX3/NyJEjeeWVVzh//jzlypXjpZde4r333nP2UwkhhBAuZZg9B+3atdwOCmL3fz6kngI6iT0KzOk5HwUlOR9CCCG8wf92/EOd9vFEXz3P5/f3ZdJ9vWRjOBscuX7L3i5CCCFELgnJKSS9/gHRV89zJrQ005s8DMjGcM4iwYcQQgiRg96g8NXsTbyy5VcAPn3gGTL9AwHZGM5ZnJ7zIYQQQhQG1spgtx27zFPLvqf4rRskRVdlUa0HzB6Xc2O4nJUsvlRWW1ASfAghhPA5CckpfLB4n9nGcaZ8Dt3xYzy5+38AjGnzPIrG8iJBzr1fbJ1P8kPykmUXIYQQPiUhOYUBsxLz7FhryucoP/FT/A161sU2ZFtMnNXzmPZ+sXc+yQ/JS4IPIYQQPkNvUPhg8T4sZWsoQJWLJ6m+ahEAX9z/tMVz5NwYzt75QPJDLJHgQwghhM/YduxynhmKnIZs/BmtonCkRXv2RFe1uzGcvfPlzA8Rd0nwIYQQwmfkzNPIrcrFk3Q5uAmAk0OGq9oYztb51D6vL5KEUyGEED7DlKdhiam0dlm1+4hoUJ+2lUvSvlaUzQoWW+dT+7y+SIIPIYQQPqNpbCTR4UGkpt00y9OIuZpK933rAJjXvi/fxUYC9jeGs3Y+Ew3G2ZKmd84njGTZRQghhM/QaTWM6lYLwCyf4+Wtv+GnGFgX25BeL/dU3Z/D2vlyfm/KDxF3SfAhhBDCp3SMizbL5yj972Ue27MSgKD33nW4L0fu85nkzg8Rd8nGckIIIXySqSNp5CcfUv3br1Di49Fs2gSa/M1S+HqHU0eu35LzIYQQwifptBrio4Phj1kAaF5/HTSafAcR9vJDxF0SfAghhPBdP/0Ely9DbCz67j2YtPIwMzYd4+qNrOxDpE2680nOhxBCCN9kMMD48QDsf6IfjcauZvzKQ2aBB0ibdFeQ4EMIIYRvWr4cDh4kq3goj2XW4Or1LIuHSZt055PgQwghRKGmNyhsPnqJhUln2Hz0kvoA4b//BeC3eg+REVjM5qHSJt25JOdDCCFEoZXvrexPnIClSwGYVush1c8nbdKdQ2Y+hBBCFEoF2sr+229BUdhUsS7HIu9R/ZzSJt05JPgQQghR6BRoK/usLJRvvwXg5/qdVT9ntLRJdxoJPoQQQhQ6BdrKftEiNKmpXAiJYEXVZqqeT4O0SXcmCT6EEEIUOgXayn7qVADm1X2ILJ2/3XOUKOYvbdKdTBJOhRBCFDr53sr+8GFYuRJFo2FuvQ52H/9Yw/KMe6yuzHg4mcx8CCGEKHRMW9lbCwk0WMnRmDbN+N+OndBXqGj18QBRYYESeLiIBB9CCCEKnXxtZX/zJsyYYTxmwMs2H68B3u9eWwIPF5HgQwghRKHk8Fb2v/8Oly5BTAx07uz444XTSM6HEEKIQqtjXDTta0Wp24X2TqIp/fuDTuf444XTaBRF8apG9enp6YSHh5OWlkZYWJinhyOEEKIoOHQIqldH0WrZuWEXZ4qVkEDDyRy5fsvMhxBCiKJv5kwA/qramD6LTgAnAJWt2IXTSc6HEEKIok2v58Z3xkTTn2u0NbtLVSt24XQSfAghhCjS9CtXEXwuhatBxVlVpanZfXZbsQuXkOBDCCFEkXb5v8beHotqPkCmX0Ce+222YhcuIcGHEEKIoistjRL/WwrAb3UetHmo2pbtouAk+BBCCFF0/forfjdvcqhkBXZHVbV5qNqW7aLgpNpFCCEwbtEuvR6KoB9+AOB/TTqg0WiwlNWhwdhYLE8rduEyEnwIIXxeQnIKHyzeZ7ZFu5RgFgGHD8OmTaDVUvvNV2D5GTRgFoBYbcUuXEqWXYQQPi0hOYUBsxLNAg+QEszCSG9Q2Hz0EguTzrD56CUMd2Y96NCBVg/UY0i7aoQH+5s9Rlqpe4bMfAghijxrSyp6g8IHi/dZnIpXMH4q/mDxPtrXipJPxV4u9+yV1qBn87TplAWS2vZgwLjVZgFmRLA//VpUYlDbqvKz9QAJPoQQRZqtJZXw4IA8Mx455SzBjK9c0g2jFflhmr3KGUTGn9xD2bQLpAWG8GRqGTL9zH/OaTeymLDyMNWjQmXWwwMk+BBCuE1BkzodfbylixLcXVJ5rkUlVc8rJZjey9rs1SN7VwOwuGYrq709ZGbLcyT4EEK4RUGTOh19vJollflJZ1SNXUowvde2Y5fzzF4F37pJh0ObAfijdltLDwNkZsuTJOFUCOFyBU3qzM/jLV2UclKAyxlZRIYEYO0zrwZjgCMlmN7L0qxU+yNbKX7rBifDy5J4T418nUO4lgQfQgiXsjcDAbb31cjv49VeUHrWLweQJwCREszCwdKsVM+9awCYX7sNaOz/7GRmy/0k+BBCuJSaGQhb+2qoffz4FYfYfPRSdhCi9oLSvlYUU/o2JCrc/HgpwSwcmsZGEh0elB0sRl5Po9WxRAAW1mpt87Eys+U5kvMhhHAptTMQ1o5T+/hJa44wac2R7DyQ9rWiiA4PIjXtpt2uljqthva1ovIkswJsPnpJup56MZ1Ww6hutRgwKxEN0HX/evwUA7uiqvJPyfJ2Hy8zW54hwYcQwqXUzkBYO87RKXFTHsiUvg3NLkr2ulrqtBqzpEPpelp4dIyLZkrfhnyweB8P710LwILarW0+RquBSb1lZstTZNlFCOFSuafFc7M39W3v8bnlzAPJ75KKdD0tfDrGRbPxkRgapBzEoNWxuGYrm8cbFCgRkrcEV7iHzHwIIVwq97S4o/tq2Hq8NTnzSDrGRVtcUrH2fNL1tPDSzZ0DwIXm93MxpITd46XKxXNk5kMI4XKmafHcMxAlQvx5rkUlwoMDrFa72Hq8PaaLi2lJpUf9e4ivXNJm0FDQBFnhIYoCs2YBkP7IE6oeIlUuniMzH0IIt8g5A7FiXyoLks5yOeMW3206znebjtvNp8j5+E1HLjBpzVG7z5mfi0tBE2SFh2zbBkeOQLFi3Nu/L9GTt6lKNhaeITMfQgi30Wk1pN24xYxNx7mcccvsPjX5FKYZjKHtq+crjyT3rqeWZlsKmiArPOTOrAcPP4wuLJRR3WoB0r/FW7kk+Dhz5gx9+/alZMmSBAcHU6dOHXbs2OGKpxJCeCFrF/mCNhwzMeWBgPqLS0JyCi3Hrab39C0MnptE7+lbaDludZ5gp6AJssIDsrJg7lzjv/v2Bawv1Un/Fu/g9GWXK1eu0KJFC9q0acOyZcsoXbo0hw8fpkQJ+8k/QojCz127yOYsr8x5zigLyzf2NpjLeTEqaIKs8IAVK+DiRShTBtq1y77Z0WRj4T5ODz7GjRtHTEwMM2bMyL4tNjbW2U8jhPBC7t5FVs3FJT/VK44ENsILmJZcevUCP/PLWu7+LcI7OD34WLRoER06dODxxx9n3bp13HPPPbzyyiv079/f4vGZmZlkZmZmf5+enu7sIQkh3MBTu8jmvriYlnxMwYhBUfI12yKfmguJa9dgwQLjv+8suQjv5/Tg459//mHKlCkMGzaMt99+m+3bt/Paa68REBDAs88+m+f4sWPH8sEHHzh7GEIIN3NkF9krGbdcUoVgacknIthf1WMtzbbIp+ZCYP58uHEDqlWDxo2zb9YbFAkcvZjTgw+DwUDjxo0ZM2YMAA0aNCA5OZmpU6daDD5GjBjBsGHDsr9PT08nJibG2cMSQriYI7vIzth03On5FNaWfK7eyFL1eKleKXz0BoVr038gAjjV6WHKKaDTSGv8wsDpwUd0dDS1atUyu61mzZr8/vvvFo8PDAwkMDDQ2cMQQriZI7vINo2NVJdPceUKLF8Ohw/D6dPGqoYyZaB8ebj/fqhTB7Ram0s+9qiZbZFP0d4nITmFST9vYOGmdQD0uVGFrHGr6V4vmmnrj6lKLhae4/Tgo0WLFhw8eNDstkOHDlGxYkVnP5UQwouYSlQLsousTqsxdqpcvhy+/BLWrIHbt60+Z1aJkpx+sDO7OjxKSprjf87UzLbIp2jvY5rlem77CnSKgcRy1TlZIhrSbvLN+mMWHyOt8b2L04OPoUOHct999zFmzBieeOIJtm3bxrRp05g2bZqzn0oI4UUcLVG1mE+xcycMHQobNmTfdL1qDYJaxqONiYGAALhwgQs7dhOyfTPFrlwi9refiP3tJypFV+Wbpo+yvFo8Bq3O4hgjgv3NlmHsVa84UqIr3CPnLFfPvWsA+KN2W1WPdaSUW7iW04OPJk2aMH/+fEaMGMGHH35IbGwsEyZMoE+fPs5+KiGEl8l3iarBAJ9/Du+8A7dvk+nnz48NuvBz/U4cj7zHbKYhITmFAUGJ+MVn0fTUXp7YvYKOhzZRP+UwUxZ+wrES0Uxv+gi/xz1Ipp/5rqWTn2qIVquRDeYKMVNic5WLJ6lz7ihZWh1La7R06BzSGt/zXLK3S9euXenatasrTi2E8HIOl6heuwZPPAEJCQD8Wb0FH7btT2pYqexDUtJu8vKsRPrdV5GFu1JQgCydP5sq1WdTpfpEXn+RZ3cu5tnEJcReSWHM8skM3fgzMxp1Z1aDzlwLKk5UeBDN7Wwql5MjG8zJp2j3MQUOD9+Z9Vh7b2OuFAt36BySXOx5srGcEMLpVJeoXrgAnTvDjh0owcGM7fQK06q0Bo3lAGHGXycs3n65WDjj7+/LN80e5cnd/+OFbQu459oF3lz/I69s+ZU59TpSZcw7Ds1QyAZz3qlMaBAaxUCPfWsBmF+7jerHyoZy3kM2lhNCeEZKirFiZccOKFWKPbMXMa1qG6uBhxrXA4KZ0bgHD7w0naFdhnGgVEWK37pB/+3zadM5Hp55BjZtMia12iEbzHmnprGRdLxyhPLpF0gPKMaqyk0sHicbynk3CT6EEO539Sp07AgHD0JMDGzcyLFKNZ12+hHd69D6o2Fc2bwD/eIl0KqVsUz3p5+gZUuoWxdmzIAc3ZVzkw3mvJNOq+Hty8aNSpdVb0Gm/91WDZo7Xy+1ipUN5bycRlFUfARwo/T0dMLDw0lLSyMsLMzTwxFCqGSrF0bO+6L8FZq+0gfN+vUQFWWcibj3XjYfvUTv6VsKNAbTtPrG4W3zfrrdtg2mTjXufnrjhvG26GgYMQJefBEs9BsyVbuA5eoduZh5wM2bxt+btDReeeFz/ixZI/uunInJ0pvF/Ry5fkvwIYQoMFu9MIC79ykKE5Z8Ts9968gqHor/xg3o69Rl27HLpKbdYPTS/VZbr9ujOiC4ehWmT4cJE+DsWeNtlSrBJ58YE19zLftInw8v8/vv8NhjUL48+mPH2XbiqgQYXkKCDyGE21jrhZG71wdAvx0LGbVqOrc1Wp5+8iPqPt2DRbtSbFaVqOVwQHDrFnz/PXz4oTH/BKBDB/STJrNNE2F2QQPkU7SH5J7BaP76C2gWLoDhw40Bo/AaEnwIIdxCb1BoOW61quCh6alkfp77Dv4GPR882J8ZjXsU+PkjQ/wZ2bU2UWEFCAiuXzf2GBkzBjIzyfQLYOJ9vZje9GGydP6qgxqZ5ne+3LNOETfS2T75Gfz1t2HPHoiL8/AIRU6OXL+l1FYIkW/2emGYRNxI56tFn+Jv0LOg1gPMaNRd1fkD/LTcum3Ic7vpkj7m4ToFX/ooVgzee4/1DduiGzSQFid28+b6H+m5dy3Dug5jL1XsdjOVpRnnszSj1uXARvz1t9lb5l5OUZKOHhudKCipdhFC5JuqHheKwsfLJxP172WORpZnRIdXVZfTWgo8ACKK+Ts12VNvUBi+J5M+T37MkK6vc7FYONUunWT+T6/z8pZf0Rr0fLB4H3pD3oli00UydxBmasGekJzilDH6EmvdZXvuXQvAgtqtrf48ROEgwYcQIt/U9Lh4ZO9quhzcRJZWx+Bu/+FGQMH7YgT6aWlfK6rA5zHJnsHRaFhQuw3tXpjCn9Xuw9+gZ/i6mcz49X2up15g27HLZo/TGxTeX2S9BTsgF8l8sDSjFnM1lSZn9qHXaFlY84Hs7rKicJLgQwiRL3qDgsGgEBHsb/WYqPSLvL/iGwAmtHiK5KgqTnnu1PRMp154cs/gXA0O45WeI3ij02Bu+AXS6vjfLJ45hJs7Es2Om7T6MKnp6lqwC/UszaiZNpHbVLEe50ON3XNX7Et167iE80jOhxDCYZZyHPJQFD5cOZWwW9f5O7o6U5s/5tQxOKutud6gcPGahWZjGg2/1m3PnqgqfDP/YypeTaXc849A6YXoW7dh0uojjF952K1jLapyJ+seu5BhfoCi0PNOO/UFtVtn3/z9puPGjqeSV1PoSPAhhHCItdLa3Doc2sxDh7eQpdUxvNOr6HNtc9+1bjRLduc/H8IZbc3VBFEHysTS45nxfLtkHI3/ScLQsRMjH32T2RWbu3WsRZWan0Hd1MNUvnyGG36BLK8an3277CxceMmyixBCNVvbzJsU89cRmpnBhyunAjC12WMcKl0pz3Hta5Xlv081wNFrhrPamltLFLX0fGnBoVz+5Q9S23dBm3WL0fPG0GX/BlXPIy3YrVP7MzDtYPu/qs3JCCyWfbssaxVeEnwIIVRTU1p7PUvPkI2zKfvvZf4pUY5J9z1p8bgyoUF0rluOSb0bqn5+RzcH0xsUNh+9xMKkM2w+eik78VNNEGVi2hPkwQaVePSB15hb9yF0ioEJSz6n7ZFtdh8vG5lZpvZn4Ke/Tbf96wHrO9jKslbhI8suQgjVVqpI8Kt88RTPJC4BYFT7l8n0CzC7P/e25p3rRjNV2zDP1HtEMWMi69XrWdm3RTnQO8NW743w4ABV/UlGdqnJ/7WIRafVsPnoJc5cy+LtDgMJup1Jz33rmLJgLH2fHM32GMvNroa2q2ZzrL7cmExtj5jW/+yk1PU0LhSLYENsA4vHyLJW4SPBhxBClYTkFL7bdNz2QYrCqFXT8DfoWVGlGRtjzWc1rM1cdIyLpn2tqDwXYshfW3NreSmm3hv/d19Fu+cAKBUamP18pk/XBq2O/3QeSrGsTB46vIVv5o+hxzNfcirCvPQ3KiyQQW2tV/f4emMytbMVT+xZAcAfcW3z5A3lDmRF4SHLLkIIu0xT5Pa0P7KNVsf/RgkIIOCr8Q5ta67TaoivXJIe9e8hvnJJdFqNxdvUjtVa7w0F+OGvE3bPA+afqHP++7bOj9e6/YfdUVWIvJHOd799SGjm3QoNDfB+99pWx6umMZm1JaOiQs1sRel/r2Qvbf1ap53ZfY4uwQnvIjMfQgi71EyR++lv89ba7wHQDBvGA53i2djB/csKasZq7zJu6RN109hIosODSE27iQLc9A+i/yPvsvDHYVS7dJIvl3xJ/0feJToi2Obshb3gSAO89cce3l+0z6yHSFGbFcn9flrSc+8a/BQDieWqc6RUBbP7HFmCE95Hgg8hhF1qpsif2LOCypfPcKVYODt69KM9d2cz3KmgyYfWPlHrtBpGdavFgFmJ2Tv2ngstRf9HRvLbz2/Q/shWfkz/ixZjP7YZYNkLjhRMeS5ZZrebZkWc2Vbek0zv58uzEi0foCg8vmclAL/UaU9UWCC9m1agUqkQn8uPKYpk2UUIYZe9KfLgWzcZsnE2ABPv68WLCw57bE+TgiYfRoYEWL3Ad4yLZkrfhmbLSXuiq/JVlwEAtPr2M3TbbVfA5Dc4Kort2jvGRTO0XVWL99VPOUS1Sye54RfIkpqt+OLx+gxuV82hJTjhvWTmQwhhl70p8he2z6dMxhVORETxc33jXqOeav6kZjrflne71LQ5s2AxObZSJ+idCr/+Cr16we7dEBpq8fEFCY5y9rVw94ySq1QqFWLx9id2GxNN/6x+H/8GFuNihoUutKLQkpkPIYRdpilyuLssYRJ+4xovbvsDgM/vf5osnb9Hmz/ZGqsaUeHBqp7DLBFWp4Xp06FSJTh+HIYNs/pYU3BUkJCsKPW1sBSMBWXdpNv+dQD8Wre91eNE4SXBhxBCFUtLDgD9t88n9NYN9peuxJKa95vd56mLpLWx2pqEKXDn1PBw+OEH0Gjg229hyRKLhxU0OIKidSG2FIx1OvgXobducCIiim0xcdIltgiS4EMIoVrHuGg2Dm/LyC41AShxPY3/27kYgPEt+6BozP+kePIiaRrrnP7NmdirPnP6N2dS74ZoyHvRd1rZ5gMPwNChxn+/8AJcumTxsPa1ohjSrhrhuXYEjgoLJKKYv9WgxFmt5b2JpWDM1NvjtzrtUDRaKactgiT4EEI4RKfV8H8tYokOD+LFbfMpfusGyWUr87+qdzda85aLZO7lkc51Lc+I2Oo/4rCPP4aaNeHcORg+PM/dCckptBy3mvErD3H1hrGiJSLYn6HtqrLprQf55JE6gAsDJC+Uc6Yq5moq8Sf3YEDD+vjORaa6R5jTKIriVWnT6enphIeHk5aWRlhYmKeHI4SwYtX6ZOLbNaZYVibPPfoeq6s0Be5eJL35ouHytuYbN8L9d5ag1q2DVq0A651Xc79nRb37qbX3X29QSHntDcpP/oKrLdsQum5VkQu0ijJHrt9S7SKEsMrWRfrBFfMgK5P991RjdeUm2Y8pDM2fXNl/RG9Q2BZdk+hHnqLSH7NRXnoJTVISev8Au83FTBVC1trNF4ULsc3Aqnopyv9hLNmOePVl20k6olCT4EMIYZHNi0TF4jBpEgDVJoxhToN4j18kvWGTtpzvWVhMd1YV+5PSBw5w+PWRXBw63G5zsZxltJ5o0OZq9vbc+aPMWRqkpEDZstCzp8VzeMPPWRScBB9CiDzsXSSW6bdR4+pVqF4d3SMPE6/1bPqYNyxT5H7P0oOKM/rB/ny1+DMqTJ3A/+IeUHWeolRGm5OatvL6yf813vDCCxAQkOc4b/g5C+eQhFMhhBl7F4mA21mUnDbZeMObb4IXBB72NmlzNWvv2aKarVhfqQGB+iyafPouqEixK0pltDnZayt/76VTNP4nCUWrhRdfzHO/N/ychfNI8CGEMGPvItFj7xpKp18is2w09OnjxpHlZS9QAve0I7f6nmk0vNNhIDf9Amh6LImnj6z3mTLa3Lvy5twkz5I+fy8DILXlg1DBfBM5b/k5C+eRZRchhBlb0/5ag56Xt/4GwOE+/YkLDHTXsCxSs0mbO9qR23rPTkVEMbFFb4avm8nwVd+xMKYR14KKm11I7ZXRFrY8B0vLI5Eh/laPL3brBo8lrwLg6rPPk3sBxVt+zsJ5JPgQQpixNe3f4dBm7r1ylqtBxcl4pp8bR2WZ2vwIV+dR2Fsq+bZJTx7ds4oql0+z6NJKetfqZXYxtVUhVNjyHKzlC13OyLJ4PMDje1YSlpnBiVLlqfbMY3nu95afs3AeCT6EEGasbsymKAy4M+vxR3xPnq1T0SPjy0ltfoSr8yjsbWZ3W+fPVz1e5asZw6k0ZwYbd77KtuL32J3JsJf46229VPQGhfcXWV4eyUnD3eUSrUFPvx2LAPj3pVfQ+enyHO8tP2fhPJLzIYQwY23vkRYndlE39Qg3/AKp8N6bXjHtb2+TNnflUdjar8X0fedhT8Ojj4Jej+61V4m/N9Lm9vCFMc9h0urDdnM7AEqE3K1kefDodipdTeFWWAS1R7xm8Xhv+TkL55HgQwiRh6WN2V7a+jsAc+s9xMjN572iukDNRd9d7citbWZn1rr9yy8hOBjWr4e5c22ez5E8B2+QkJzC+JWHVR07skvN7D13Pj+zBoCAAS9BSIjF473p5yycQ4IPIYRFHeOiGdnF+Ae/ysWTtDr+NwY0fNe4h1eVN6q66LtxLLk3s9s4vO3dMVSoAO+8Y/z3f/4D165ZPVdhynMwzdKoFRUebNxz53YK4Vs3gU4HgwbZfIw3/ZxFwUnOhxDCIr1BYfRS4wXl2UTj9vArqzbjdEQUYN4O3NOfOL2pHbndzqSvvw4zZsDRozB6NHz6KZC3oqVUcXWVRN6Q52BvliYns+WRjz4y/vepp6B8ebuP9aafsygYCT6EEBaZLihhN//l0TtlkDMadc++39vKGwtNO/KgIJg4Ebp2hfHj4bnnSLgdnqeiJSoskIhi/qRdz7KY96HB+KnfG/IcHJl9yV4e2b0bFi4EjQbeflv14wvNz1nYJMsuQgiLTBeUJ3b/j2JZmRwoVZHNFepYPU44oEsX6NYNbt/mYr8XGfDTzjwzB+fSM7l6J/Dw9jwHtbM0Q9tVu7s8MmaM8b+PPw41arhoZMJbSfAhhLCoTGgQWoOeZxOXAvBDo27GT6kWjhP5MH48SmAgpbZsoMOhv/LcbQo6Ior5UzbMe/McEpJTeP2XJLvHRYUFMqhtFeM3Bw7AL78AsOuZgdldUL2pcke4liy7CCEsahobyWMpScSkneNKUCgLarc2u9+bpv0LpcqVOdN/EOUnfcHIVd+y9t5G3PQ3DzIU4Or1LH5+viFarcbr8hys9SHJyTTK97vXvjvmd94BRWFdzft4dsM1IAnw7uZpwrlk5kMIYZFOq2H4of8BMLdeB7MLo7dN+xdWSU+9zOmwMtxz7QKvbP7V6nEXMzKN1SE2+oK4m60+JDmZZmna14pi89FLbPhhAfzxB3qNlo/izfcG8qYqKuFaEnwIIbLl3Axs158bKLltEwadjuUPPGx2nDdN+xdmJcuUYPSDLwDw0rbfqXjlrMXjvHFpS22Fy+eP1QOg5bjV9J62mWLvjADglzrtOFzavEuutzZPE84nyy5CCCDvHiJjEr6mHnC+bSd+H9tbyhtdoGlsJMMat2H938todfxv3ls1necfG5V9v6uXtgqyYZ3aRONVB84xY9NxFKDTwU00OnuA6/6BfHl/X4vHe1sVlXANCT6EEHnW7iNupPPw3rUAvFa6Jc/tS5VZDvJ/sbb2OJ1Ww6jutfng2Ess+34QDx7dTtsj21hdpanNpS1n7HJb0A3r1M7GLEg6iwIUz7zOyNXfAjC9ySNcKG47oJIqqqJNgg8hfJyltfteu/5H8O1M9pa5l+3la3PKS5qJeVJ+L9b2HtcxLhoGd2fuoTU8s34eo1ZNY1Ol+kSWDLN4bmfscmstUTQl7SYvz0rkv081oHPdcjbPYW8zPQ1QIsSfyxm3ABi+7gfKXbvI8YhopjR/1O4YvXGpSTiP5HwI4eNyr93rDHqevlNeO6NxdxSNxqv2EPEE08U6d46DvQRJtY/rGBdNn8XTuVU2iopXU1mh32belr2A48hJTaLooDl/8+fuvOfKmRO07djl7Pb71vqQPFz/HgCanErm6b//BGBEx1fzVPXkfqxsElf0ycyHED4u9/R2+8NbuOfaBS4Fh7G4Ziurx/kKe7vLWmsz7+jjdGGh6CaMh969qTB1Agx+CSpVcso4ci7RGBTFbqKoQYFXZicyVXs3qdjajMuLrWJZtCvFvDvrnZmY8OAAflm9l0+XTQRgdr0ObK5Y1+rzShWV75DgQwgfl3t6u9+ORQDMrt+JTL8Aq8f5Ckd2l82ZIJmvxz35JMo336BZu5azzw/gxLRZ2fkc+TmfpYAhIthf1euGu8HMin2pFpdpUtNuMm39MSY/1YASIYF5clD0t/VMWj6R2CspnA4rzSet+9l8vijp8+EzJPgQwsflXLuvde4ozU7vJUurY1aDToA0E8vv7rL5eVzC3lR+qtuHmevWU251AqPenMCwxq0Z1a0WmbcNDp3PWl7H1RtZqs4DxmBmy9FLdmdcRi/dz8bhbfPMVug+HccD+/8iU+fHgJ5vkx5UPPs+zZ3HD21XlUqlQqSKyse4POfjk08+QaPRMGTIEFc/lRAiH3RaDaO6Gdfun925GIBl1VtwLrSUTIOjfsYn93GO7kprChY2BUczvekjAIz+33/JOH+JAbMSOX4xQ/X51DYAU+OvoxdVz7iYmTMH5d13AXiv/QD2RFc1uzsqPIipfRsyuF01r2qeJtzDpcHH9u3b+eabb6hb1/oanxDC8zrGRfNt54r02L8OuLOPC9JMDO7ODFm7LFpKkFSz30nOx+UOFia06M0/JcoR9e9lhq+dAcCcbSeJClM3Dke2uLfnh83HVR1nNtPzxx8Ynn4ajaLwY4MuzKvXIc/xI7vU9OnfK1/nsuDj33//pU+fPkyfPp0SJUq46mmEEA7KWbGQczOvBzcsJPB2Fv/Wqc+zw3oxp39zixUXvibnzJCa3WVNMxip6ZlWz5n7cbmDhUz/QEZ0fBWAPkkJND+xi9T0THo1ibFa1przfGqXfNTMM2Rk6lWdK3vmZ/ZslF690Or1/BrXjlHtX7L4vKOX7pcupj7MZcHHwIED6dKlC+3atbN5XGZmJunp6WZfQgjXSEhOMba5nr6FwXOT6D19Cy3HrWb53yfhv/8FoPibr9OjQXmZBs+hY1w0U/o2JCrc9u6yju53YnqcpWBha4U6/Fy/IwCf/TmB0MwMfvjruKrzqV0qGvxgVfsH2ZE941IhHN58E/r0QZOVxcKaDzC806somryXGatLNcJnuCThdO7cuSQmJrJ9+3a7x44dO5YPPvjAFcMQQuRgLQExNe0mi9+fTIezZ6FsWXj8cY+Mz9t1jIumfa0om51FHdnvpEXVUtnfWwsWPm7zPC2PJ1HxaiqjVk7jP12GWjwu9xKGmgZgUeFBvPpgVWpEh+apiIkM8edyhvrE1NGtyqHr3g0SEgA49NwghpZsj0Grs/k4Xy3fFi6Y+Th16hSDBw/m559/JijIfvQ9YsQI0tLSsr9OnTrl7CEJ4fPs9Yj4vzuJpoaXXoLAQKtLM75Op9XY3F1W7cV0+b5Us/fVWl7J9YBghnUZhgENjyWvosPBv/Kcy9IShiNLRR3jotk4vC1z+jdnYq/6zOnfnJFda6t6HeHBfnxYVUPz3p0hIQElOBjmzuXS2+/bDTzAd8u3hQtmPnbu3Mn58+dp2LBh9m16vZ7169czadIkMjMz0enu/lIGBgYSGKguK1wI4RhTg6mNhy9Y/UReJ+Uwjc/s55bWj92dnuSiE9p3+yq1F9MfN5/gx80nzN7XUd1qMWBWYnYJqsnO8rX4ptmjDNj6G58um8jesvdyOiIq+35rfUZMS0W5f5aWemmYgiqTzUcvqXodbQ5tpeeYTyh+6wanw8rw9jMf8lTtVrRXOfPiq+XbAjSKojj1I821a9c4ceKE2W39+vWjRo0aDB8+nLi4OJuPT09PJzw8nLS0NMLCwpw5NCF8iqUGU5Z8seQLHt27hj9qt2HFW5+SkHwuzwXD9GnZ1ytf7NEbFFqOW231optb7vfVWlOwf/+9wS+zh9Pw7EGSoqvyeJ9PydKZNwub2Ks+Pe60M889Jkc3obP3OjSKgUF/zeP1jT8DsCUmjld6juBKsfDs1wMwYFYiYB5Mye9S0eXI9dvpwYclrVu3pn79+kyYMMHusRJ8CFFw1vI7civ97xU2TelHgOE23Z/5kt3R1awea/q0aqmZlLjL9N4DqgOQnO+rpXbofb7dyj1p51n6w2tE3PyXmQ27MKr9ALPzzOnfXPUW9GoCEmuvo9itG3y+dDydDxmXgH5o2JWP2r7AbZ1fntezYl+qzKL5EEeu39LhVIgixpEGU08lLSPAcJud5WrYDDzA+vS+MGdtucOa3O9r7iUQvUEhOjyIs5RhWJdhfP/7hzybuJTDpSoyq0Fnh5cw1O6Ka+l1VLxylm8XjqXquWPc0vrx7kOv8Eu9h6y+HjVJusI3uSX4WLt2rTueRgiB+oqLgNtZ9ElaBtxtKqaGVCjYl/Oiuyw5hR83n7D7GGvvqyl5dMCsRNZUacpn9z/NGxt+4v0VUzkVXpb19zZS3YHWVsXTgFmJeZZCsl/HP5cInPMz9Sa9gy4jgwshEbzU8x0Sy9e0+3pyB1NCgBvaqwsh3EttcNB9/zrKZFwhtXgky6q3UH1+qVBQx3TR7aRyecHW+5qzz8jk+Cf4Pa4tfoqB/y4ax+w6iqolDHsVT2DcSC53ZZPu7BniRwyg4XtD0GVkkNb0Pro+O8Fm4GHv9QghwYcQRYyqP/qKwvPbFwDwQ6Pu2ev1tlhqIy7sy097dkuyS2JfjCfgu+mkNW9JSOZ14l/pA1u22B2HI7viAnD9Onz6KdSoAb/9BjodjB5N8Y3r0JYvX+DXI3ybBB9CFDGmi50t9x//m5oXjpPhH8TsO100bZEN5vLP0fbs9s4VX7kk3ZreS/jKBHjgAUhPhw4dYMUKm49VOyN2IfUSx0aO4WbFWBg+HDIyID4eduyAd99F5+/ntNcjfJcEH0IUMaaLna0//S/cmfX4pW57s23OrZEN5gpGbXt2h4SEwNKl0Lq1MQDp2BE++wysFDDamxErmXGVoRtm0ap9Y2I/eoegi+c5HVaG0Y++QcLUX6F+fde+HuFT3FJq6wgptRXCOaz1+ah24Tj/+34Qeo2WB16cZtawKqeh7apSqVSIVCg4UX56btg73/YDZ4l++3UqLpxnvLFdO5gwAWrXznOspd4dVS6e5Lkdi3g0eRWBemNL9ZPhZZna/DF+rdOO23f6iVgKKpz9ekTh5nV9PhwhwYcQ+Zf7YtCoYgl2nrhCatoNLmfc4vil69R5byhP7FnJ0uotGNhzhMXzPNeiEu91U9diW3iGWXCpKPT9+0/eW/0tAfosY37GU09B377Qti34+WU/ZsBPO6l28QQtju+i+/611E85nH3OpOhqfNP0EZZXizdrjy49XoQa0udDCB9kq3/Dww3LA7Bjyz7q7FsLwHdNelo9V/talmdDhHfIUzKr0TCrYRfW39uIEWu+p9Ohv+Cnn4xfwcFQsSKUKUPHy5c5ePI0AelXs8+VpdWxoXozpjTozvbytUGTN7iQHi/C2ST4EKIIUNu/oeHin9HqjU3FEu/JWyope254P1slsycjonjl4bd5MO0fprEP7W+/wcWLcOCA8QsIAJTgYNIaNuPUfa25+diTpPmHsn1ekt3nlh4vwlkk+BCikFPTv+H9RXtpXykM3dSpAHzb9OE8G5hJpULhoKZkdmX4vWzt/xTxX38Nx47BiRPGIKRkSShTBk3NmkQEBBBx5zG3VW4kJ707hLNI8CFEIaemo2lqeibr3/6MNpcvQ2wsPd5/haQ/D9rd7VR4H7WzD+ev3QS/klC1qvHLhqayC61wMwk+hCjk1FyM/PS3qfKjcdaDoUPpWK887evcI5UKhZDa2QdHZilytnCXGTHhDhJ8iDykfK5wUXOR6b5/HTFp57gcEkF4v+fQIXtuFFaumqWwtiGepRkx+RshCkqCD2FG7Y6XwnuYLkbWll40ioFXNv8KwPTGPWh17ibxxUPcOUThRK6cpVCzC638jRDOIB1ORTZTxUTui5ipYiIhOcVDIxO25GzfbUnHg39R5fJp0gJD+KlBF6lYKAJc2WHUNCPWo/49xFcumSfwkL8Rwhlk5qOQc9b0p72KCQ3GHS/b14qS6VUv1DEumqHtqjJ+5WHzOxSFgVuMsx4/NOrGv4HFpGKhiFAzS+FM8jdCOJMEH4WYM6c/HdnxUvIEvNOgtlWZs+0Uqel3f46t/9lB3LmjZPgH8UPj7rLbaBHjzrwd+RshnEmWXQopW9OfL89KZOLKQyxMOsPmo5fQG+x30HeofE94JZ1Ww/vdjRvKaQAUhUGbfwFgdv1OXA0Ok4oFkW/yN0I4k8x8FEJqmkrlnH7PORtibZmmVPFAVc8tU/beLWfFQoU922l8Zj+ZOn8WPtiLKX1kt1GRf64o8RW+S4KPQkhNU6mcTMlgL7aKZdGulDzLNN3rRbMw6azNc0iTocLDlAtwrdVHAFx5si8LP35cZjxEgUgjMuFMsuxSCDk6ranc+fpm/bE8QUtK2k2+WX+M1PRMq4+XJkOFj27nDiI2rQWdjqiP35OfmyiwnFVVuX+b5G+EcJQEH4WQu6c1nVG+J9zs44+N/+3TBypV8uhQRNHhyhJf4Vtk2aUQsjf96WyfP1aPFlVLueGZhFPs2QMLFxq3Rh8xwtOjEUWMu0t8RdEkwUchlLPDoTtczLC+JCO80Nixxv8++ijUqOHZsYgiSVrzi4KSZZdCyjT9GRHs7/Lnkuz1QuTIEZg3z/jvt9/27FiER+kNCpuPXnKo5F4Id5GZj0KsY1w0oUH+9Pl2q0vOL9nrhdC4cWAwQOfO0KCBp0cjPET2XxHeTmY+Crnm95YkOjwoT/Z5TloN9L8/9m7zKRUke70QOnUKZs40/vuddzw7FuExf+5O4WXZf0V4OZn5KOSy8z9+2kmDswdpefxvIm+kE5aZwYWQEiSXrcwjgx6nbftaNKpYIs+nIWsiivkz9pE68impMPn8c8jKgtat4b77PD0a4QF/7j7LoDl/W7xP9l8R3kSCDw8r8MZwmZl0XPoj+378iuBUK43ClnwO3bvTccgQ2g9vy5ajlxg4O5GrN7KsnjbQT0v7WlEOvhrhMefPw/Tpxn/LrIdPSkhO4ZXZlgMPE9l/RXgLCT48qMDrsgkJMGgQHD1KMKAUL86lVg9yOboCgSUiiLl2Hu3OnbBjByxYAAsWoHv0UQL/86HNwAMgNT1T/kAVEnqDQup7Y7jnxg2u1WtIsTZt0Xl6UMKtTFsuqKWmUaGzdswWwhIJPtzM9D/0in2pfL/peJ77TeuyNhv2GAzw/vswerTx+6goGDsWzZNPUio4GFNHDr1BYfOxy9xI2k2t32dS9tef0fz+O3VXrKRD24Esr257al42iPJ+CckpfPnLVn6fMQ2AYZU7k/zpGkks9DGObrlw+Ny/bD56yWpAIQmrwtU0iqJ4Vf1Veno64eHhpKWlERYW5unhOJWl/6GtiQzxZ2TX2kSF5frEkZEBvXvD4sXG7wcONPZ1CA21+1ytMk7z9arJhO/fA8Bn9z/N5PgnjM2oLJjTv7nMfHgx087GA/+ay382zOJAqYp0eu5r0BjzyKXjpO9YmHSGwXOTHH6cpYDC9HuV+8Jg+ishv1fCGkeu31Lt4iam/6HVfjq5nJHF0HlJ9J6+hZbjVhsz1G/ehJ49jYFHYCD8+CNMmmQx8LD0XBtCytOw60d836g7AG9s+InP/5yA1qA3O06D8Y+SlNh6L9M0e/CtGzy3YxEA/41/AkWjzb5ofLB4n/R28BHHL2bk63G5K2DU7Jgtv1fCGST4cAO9QeH9RZb/h1YjNe0mr83cyvkO3WDlSiheHFavhqeftvhctv546LU6Pmz3Im93GMhtjZbHklfxxdLxeQIQKbH1bqZp9t5JCUTeSOdYiWiW1GiZfX/OxEJRtCUkpzB+5eF8PTZ3QGFv+UZ+r4SzSM6HG0xafZjU9PznT2gNeiYs/pwyBzehBAejWbIEffN4th29lCcZTO3a7+z6nbgUHM6kReN4eN9aFI2G17sMRdFoGdKumkyrermV+1Lx12fRf/t8AKY0exyDNm+aqeTtFG2OJppakjOgUPv7Ir9XoqAk+HCxgnwqAdAoBj77cwKdD24iU+fH0f/+yMmS1fhg3GqLyWCZtw2qz728+n282v1NJi0cxyN713CxWARj2j5PpVLF8j1e4XoJySl8t+k4j+xfT9S/l0ktHsn8uDYWj5XW+EWbo4mmtpg+yKghv1eioGTZxYUK/KlEUfh4+WQe2buGLK2OV3qO4LeSNS3mc5jWbh1d+02o3oLXuwwF4MXt83k6cYlDf1gs7R8he0q4TvbvlKLQf5tx1mNmo25k6cz3+JG8Hd/gzBkI0wyqrY7J8nslnEVmPlyoQJ9KFIVRq6bx1K7l6DVahnT9D6uqNCMy6azVfA4NMGfbSaLCgjiXflN1jsnC2m0on3aeNzb8xPsrp8GBblC5s93HWaqoiShmvAhevX63j4iU6DmP6Xeq5fEkal44ToZ/ED/X75TnOAXJ2/EFaj8oRIYEcCXjlsW/CTn3cMq5Y7YGzI6XLReEM8nMhws58qmk330ViQwJMP4Prii8uX4m/XYay2nf6DyYP2veT2SIP5czblk9h4KxOVjvphWAvPu4aKz8G4yVEvPqPoROMaDr2wf++cfmeK1V1Fy9nmUWeIDsKeEIe7NGpt+pF7f9AcAvdduTHlQ8z3mea1FJgj0foHam4qMecdnf574fzAMK047ZUeHmgU1UeJCU2QqnkZkPF1L7qWRou2oMbleVJpVK8srsRAZtnscrW34D4O0OA5kf9yAADWIiWHXggt3zVSpVjCl9G+aZlYi6MwMB5L0vIpiIGd/AK71g61Z49FH0Gzay7dzNPEmttipqLJE9JdRR09ipTGgQNc4fo9Xxv9FrtHzXuIfFc0lrfN+gdqaiY1w0U7TW/ybkDig6xkXTvlaUdDgVLiPBhwuZPpWkpllfAokKC2RQ2yokJKcweuk+Xtj2B//ZMAuAD9v2Z3b9TkSHB9G9XjTfrD+m6nnLhAYRX7mkzT8eVu/77Tdo2BCSkljW+lEGtXs1+7ymC2F4cIDDy0myp4Rt1ho75e542zQ2kld3GWfEllW7j9MR5kFGzil04RtMMxX2AgtHAwqdViP/rwqXkQ6nLma6qIDlTyVT+jYEYMCsRPokLuWjFVMA+LTVM/w3/gkAJvWqz8fLDti94JsuPBuHty3QJ5Rt3/9GoxeeRKcYGNT9TZbUbGU25udaVOI7C63h1ZjYqz496t+T77EVRXqDQstc1Us5mf1cU1MwVKyE9nYWDz/9BX+Xq252HEgHSl8le7EIT5MOp17E3vpp+1pRfLB4H4/tXpEdeHwd/2R24KEB3lu8V9VMgzOSDPUGhcHnSjC5+eMAjEmYRPm0c9nnB5ifdCbf55cSvbwcauz09ddob2dxuWFTUmvWMztO1uR9m2mmokf9e4ivXFICD+HVZNnFDWxNd24+eomGW1YwbtlXAHzbuAdf3N83+7EKxlbralhLMnTkE5HpQjix5VO0OLGLRmcPMGHx5zzx1CcYtLrs8djKnrdElgOsU5uYfOncJZg6FYDIkSPY2L2tfNIVQhRKEny4ibX109vr1vHl0i/QovBz/Y581PYFqxu92WMpydDR3SlNF0K9Vsfgbv9h2YxXaXxmP/+3cwnfN7mb3NgstgQJyefyJLlZIiV6tqkul/x1Dly9ClWqQLdusiYvhCi0ZNnFBUzlkvMTT/Pdhn+Y/7eVZlsHDhA/7HkC9bdJqBbPyPYDrAYe2WW4Flhr/GOtHNZW6WvOC+HpiCjGtnkOgDfW/0iFK3ePX5Z8jvBi/oQXM29uFVHMP7vXh4ksB9hmr1wSAEUhctYMAPY/9gzo8rZSF0KIwkJmPpzM0kyDidmMw+XL0KULfmlX2RNTk6FdX7e4N4dpuWJkl1oMnK2+8Y+9Deaslb7mrtCZU68DXfdv4L6Tu/kk4Wv69PoI5c6W7WnXs1CAoe2qUqlUSPbUP6B6OUCS5GyXS5o0OrOfGhdPcMMvkCdvVOP5lYcY1Laqz71XQoiiQWY+nMjaTINJimnGYddp6HOnkVelSpyf9Qs3/fN+8s0ZWHSu61jjn/zuTmm6EJqeX9Foeavjq1z3D+S+k7vpvWu52Tk0wNztp+hat1x2kpvaxLeE5BRajltN7+lbGDw3id7Tt9By3GqfbEZmLTHZpE/SMgAW1WxFelBxxq88TItPfPO9EkIUfhJ8OIkjjbdShr4FCQkQFATz5/NgqzhVgUXHuGg2Dm/LnP7NmdirPnP6N2fj8LY2czfssXRc7gvhyRLRfH7/MwCMWPM90el3G53ld4vt/CwJFXWmn+/ILjXNbi9xPY0uBzYC8HODu63UU9N9970SQhRusuziJGr3cWn1z076rfnZ+M306VC/PmC5IqZRxRLsPHGFhUlnzJYk1CQZFnR3StN4xq84xKQ1R/ihUVe6HNhAo7MHGLN8Ev0ee98sP8WRVvL5XRLyBTqthlKhgWa3PZq8ikB9FnvKVmZ3VNU8j/HV90oIUXjJzIeTqLn4lsq4wudLxwPwz+PPsDm+k9keHjmXK9Ju3OKBz9bke0nCGbtT6rQaWlQpBYBBq+PNzoPJ1PnT5p+d9Ny31uxYR/p35HdJyFfkfC81ioGnkhIAjBvI5UpI9vX3SghROEnw4ST2Lr4axcAXS8dT+vpV9peuRJ8aj1sNLJyxJJE7d8NsLHf+q6b0NWcQc7RkDBNb9AbgnTXfEZqZka8ttguyJOQLcr7n8Sd2c++Vs1wLCGZRrQesPmbFvlT3DVAIIQpIgg8nMV0wrHl25xIeOJbIDb9AXu3+Jim3zC/6psDiz91nbS5JgHGaPU/ZrgXO2J0ydxAzvenDHI0sT+mMqwzdYFw+crR/R0GXhIq6nO+5KdF0fu22XA8ItvqY7zcdl9wPIUSh4fTgY+zYsTRp0oTQ0FDKlClDz549OXjwoLOfxuNyb30OxouwpUtwpctnGL5uJgAft32eI6Uq5DnGFEq8uzDZqUsSjiSp2jqHKYjJ0vkzqt1LADzz9xJ+ahLkcP8OZywJFXUd46L5vsM9dDi8BYDZ9TvaPN6UJ6MmKBVCCE9zesLpunXrGDhwIE2aNOH27du8/fbbPPTQQ+zbt4+QkBBnP51H2Ooamnt3SY1i4NNlEwm+ncm22PrMqt/J2mkdaqXuyJKEMzphmifE1udS2jZKLl9MywnvwyNtHerKqnYbcF9PoGzz11Iw6DlbqyEHysTaPFZ2DRZCFCYu39X2woULlClThnXr1tGqVSu7x3v7rrbWtj7PuaOo6SKdmnaDqJnTiP9qNPqQEFbOW8lLGy45ZRxz+jf37EXm5EmoWROuX4effoK+fa0eaq2RmKOt332KXg/33mt8n3/8kdERDVXtJCy7BgshPMWR67fLS23T0tIAiIy0PIWemZlJZmZm9vfp6emuHlK+OVIiGl+5JJw6Bd99CYDus88Iq1EVVAQf9jZtiwoL9PiShL58DKcHDKPiFx9xa9jr6Lp0RVciIs9x9gIMaxvumT2XL3ZBTUgwBh6RkfDYY7Q7e11V8OGreTJCiMLFpQmnBoOBIUOG0KJFC+Li4iweM3bsWMLDw7O/YmJiXDmkAnG4RHTwYMjIgBYt4KWXVOc6fNQjLvt7S27eNni0usHUmbQ9jTgaeQ8BF87za7fn8yQ8qqnasdcN1We7oN7ZvZb/+z8IDpY8GSFEkeLS4GPgwIEkJyczd+5cq8eMGDGCtLS07K9Tp065ckgF4lCJ6OLFMH8++PnBlCmg1aoufzW1Us+9aZtJ2vUsj3W2zBlQ3PLz5/07yaeP/bWAL8fPzx6TvVkisJ8g6bNdUE+cgKVLjf9+8UXAeaXTQgjhDVwWfAwaNIglS5awZs0aypcvb/W4wMBAwsLCzL68ldop7Sh/BV591fjNsGFQp072fWrLX9vXiiLIz/LOpY6W3DqLpYBiQ2xDEqrF46cYeG/VND5YtDd7maQgVTvOCF4KrW+/BUWBtm2hevXsm51ROi2EEN7A6TkfiqLw6quvMn/+fNauXUtsrO0s/cIk946vuZl2oG3y+/fGT68xMfDee3mOU5PrsO3YZVLT1V283ZV4ai2g+KjN87Q5uoOWJ3ZRd/sath1rUOBGYo4EL0WquiMryxh8ALz8cp671ebJCCGEN3N68DFw4EBmz57NwoULCQ0NJTXVmJsQHh5OcLD1JkmFgZoS0bFNwtF2/8T4zaefgpXyYnvlr97UBdQ0k7HMyjLH6Ygovmn6CK9tnse7a74j6UI/ypQuoerc1maTvOn1u9WiRZCaCmXLQo8eFg9xRum0EEJ4ktOXXaZMmUJaWhqtW7cmOjo6+2vevHnOfiqPsDf13XrGeLhxw5hk+uST+X4eb+kCmjPh88fNJ6weN6X545wNLUVM2jnqzfvOboIkQGSIP6npN7P3tsnJW16/u5ia1p3/bCIAhueeg4AAD49KCCFcwyXLLkVd7qnvUiGBoAH95s0wezaKRoNm4kSHGm/lpnaJx5XVDdZ6mlhyIyCIMW2eY9KiT4n5ZiKaoQOszhKZXM7IYui8JCBvfw9veP3uYipHDjx2lLVbN2BAw6O3a/NScorkcQghiiTZ2yWfTFPfgX5a/vPbLvpM30LgO28DsLTBQyQElivw+T1Z3WAr4dMSDbC0xv1cbtQczY0b8OabtK8VxZB21QgPtly1k1PuChZPv353yVnR03vXcgDW3tuIJG1E0a7oEUL4NAk+CiDnhaPNPztodiqZTJ0/Y5r1csqFw5PVDfYSPnOLCg9iytONiPzWWFbMvHm8+tJ4xq88xNUbxpbx4UF+FA+0PNlmqYKlqFd35AzwAm/f4vE9KwH4uUGnol/RI4TwaS7vcFpU5bxwaA16hq/9AYAZjbpxNqy0WbfTgnw691R1g9pEzmfiK9IpLjrHmKI5+fjTVJg3k1cXfs3y/5uIXmssGU67edvmuSxVsBTl6o6cAV7Hg5uIvJHOmdDSrLm3MVCEK3qEED5Pgo98ynnh6LlvLTUuniAtMIT/xj8BOPfC4YnqBrWJnJ3ios3Gpjco9K/anXlBv1PzwnF6JyUwq2EXh55705ELeQKNonDxzd0mPmcp9TOJxqZic+s9hEFr3t+lyFX0CCF8ngQf+WS6IPjpbzN40xzAWPGRHlTc4nGFTX4TPrcdu8zBrEC+uL8vo1dM5fUNs1hS836uBqtvHjdpzdHsfxeVjeYs7XETEmAMMmqnHqHR2QPc0voxt17HPI8tKhU9QghhIjkf+WS6IDySvJqKV1O5UCyCmQ27Wj2usMlvwqcp2JpdvxP7S1eixM1rDNvwc77HURRaqVtrE59xSw/cnfX4s0YLLhS/2x9F9msRQhRVEnzkU9PYSGJCdLz2l3HfmqnNH+NGgHmgodXAlYxbnhieU+Qn4dMUbOm1Oj5oZ9yXpE/SMmqe/ydfYyjsiZf2qoYibqTTY/86AH5scDd4LUoVPUIIkZssu+STTqthatZuyqef53xICWbV75TnGIMCA2cnMkVbeCszHE34zLlcs6VCXZZUb0nXgxsZtXIavXqPRaPREF7MnyA/nc328TkV5sRLe1VDT+xeQdDtWySXrUziPTWyb48qIstNQghhiQQfKuVOFmxaPpTaMycDMCX+cTL9A60+1hlVL57kSMJn7hb0Y9s8x4NHt9P8VDJdD2xkac37+eSROmYBzeFz/zJpzRG75y6M+TO2xqw16On7958AzGzYBTQaBrWpQosqpYpMRY8QQlgiwYcKlpIFnz+yjpEnT3KrVBlmW0gSNCnMn9rzy7Rc88HifZyhDFObPcrQTbN5d9339HjnBdrf+TRvej82H72kKvgojPkztsbc+p+dVEg7x5WgUBbVfACAqmWL+8zviRDCd0nwYYelFuNag54+a2YDsL5rXzL97O/BURg/tRdEzuWaiz2rc/PxjUSdPknU7EnQdILZsUW5lbrptVlaenk2cQkAv9Rtnz1zVhgDLCGEcJQknNpgLVmw08G/uPfyGa4GFef9e+5XdS5fvKiYlmu6Na9C0LfTjDd+9RVs3ZrnuKLaSj3na8up0uUzPHAsEQMaZjXoLJUtQgifIsGHDRaTBRWFgVt+AWBGo+6cvu1PZEiA1d1bfeWiYtqVdWHSGYu71NKhAzz9NCgKvPAC3DKvAirKrdQ7xkUztW9DIord3ePm6Tu5HqsrN+ZURBQKMLJLzUIZYAkhhKNk2cUGS0slLU7sotb5Y2T4B/FDo24A9KxfjhmbjufZvbWwf2pXy1JOjKXmYPrPv8Cw5E/8k5M5PeQtoid9Yfa+FOVW6qbXNmn1Eeat3pu9j8tPOXrDjF66H61WU6gDLSGEUENmPmywtFTy/PYFAPxapx1pwaEAtK8VVWQ/tdtjrYFW7uZgCckptPxuN6+16g9A9NSJvDJgYp7mYaalmh717yG+cskiEXiY6LQaBreryre3dxOWmcHRyPKsj22QfX9RaKgmhBBqyMyHDbkTIStfOkXbf3ZgQMOMxt3NEiF1Wk2R/dRuja0GWgpkb65nMBj7nShASo2W/Hb0QR5LXsW78z6hc7F74PmWRTpAy0mfeYsS040l2tOb9ETR3I3/c75nhbk0Wwgh7JHgw4bcPSue27EQgJVVm3GyRDnAfEmlqGyAppa9BlqmMuN3FyabBSjvt3uJZqeSiUk7x0fLJ/NB6RI+c7E9OuUHql09z4ViEcyPa5vnftN7tuXoJbRajc8EskII3yLBhx2mRMjx8zbzSPIaAL5r3EM6UKK+fPhyrhbz/wYWY0jX15k7ZwQ99q8jaXU1tj3ZoOgHbopCmWmTAJjZqKvNEu2BsxO5eiMr+/uissGeEEKA5Hyo0jEummVB+wm+ncnVGnEM+fAFNg5v6/MXgoKUD+8sX4uP2zwPwDurv0O/dq2TRuXFVq8mYv8ebvgFMqtBZ5uH5gw8QPJBhBBFiwQfaty6hXaycZ0+4u03ia9SSqbAuZsTY6vMODLE38q98EOjbsyv1Ro/xUCzN16CI/a7nBZqo0cDsLhJJ9KCwxx6aGHfYE8IIXKS4EONX36BlBSIjoYnn/T0aLyGmuZgH/WIsx6gaDS83XEQ+++phv+VS9CxI5w/78IRO5fd3iY5bdgA69aBvz+lP3wXyPue2ZOzVb8QQhRmEnzYoyjw5ZfGfw8cCAH2W6n7EnvNwTrXLWczQLnpH0TKrF+gUiU4ehS6doVr19wy9oJISE6h5bjV9J6+hcFzk+g9fQstx622uCyiNyhcfXsUAOce7U2rtg0tvmcRwdZniXLytVb9QoiiR6MoilfN4aanpxMeHk5aWhphYY5NTbvEunXQujUEBcGpU1CqlKdH5JXy7PqbqzrDbiOygwfhvvvg8mVo2RKWLYPixT3xUuyytN8PkN1kbmi7qlQqFUKZ0CCuZNzi96l/8N2UQdzWaGn94jT0FSsxqlutPKXZBkWhz7dbLTyjuTn9mxf95FwhRKHjyPVbql3smWSsTuCZZyTwsMFembHd7qXVq8Py5dCuHWzcCN26wZIlEBLiplegjr3eJgDjVx42u31mwvcALKjdhtMRUWjuJI/mbkCnNyhFdoM9IYTISZZdbNCnpGJYsACAXd2ekkS/ArLbvbRxY2MAEhoKa9fCQw8ZZ0K8iL3eJrk1P7mbB44lkqXVMbFFb8B68mhR3mBPCCFykuDDioTkFKb2G4n29m3+jq5Oj43/Wl3TF07UrJkxAImIgL/+glat4MwZpz6FQ4miuTiUb6EovLluJgBz6nXkVETU3buwnDxalDfYE0IIE1l2ycGUt7BiXyozNv7D2q1LAJhdvyNwt9eCXARcLD7eWB3SoQPs3WvMBfnf/4xLMwWgNyhMWn2EGZuO5buBlyO9Tdod2UbDswe54RfI1/dZrpKyFMwU5Q32hBACJPjIljshssWJ3VS8mkp6QDGW1LgfkL033CouDjZtMi69HD58Nwm1ceN8nS4hOYW3/tjD1etZee5zJKjMvd+PNX7629mzHjMad+NCcct5GtaCGV9r1S+E8C2y7ILlnVmfSloGwPy4NtwIuHuBkF4L7qOvUJHtsxZxtWZduHgR5YEH4E4OjiMSklN4eVaixcADHGvgZSsvI6e+f/9JtUsnuRwcxtRmj+W5X4NxxkWSR4UQvsjngw9L1QulMq7w0OEtgHGt3hLpteBapj4aj/9xlBYd3mV9pQZorl+Hhx+GTz4x9l9RwfTztceRoNJaXoZJietpDN34MwCft3qa9CDzkmFJHhVC+DqfX3axVL3w+J6V+Bv0JJarzoEysRYfV5B9TYRtuftoZAQWo9/j7/Pequk8m7gERoyAfftg2jRj/xUbHK1OURtU5s7LOH7xOhNWHgLg9Q2zCM/MYF+ZWObWfSjPY2VTQiGEr/P54CP3xUajGOi1azlgedZDei24lrU+GnqtjlHtX+ZIyRhGrfoGv59+Mu4FM38+lC1r9XyOzlA5ElTmzsuoHlWcX//7B73v/P683+4lypYIYWSXmpQICZTkUSGEuMPng4/cF5sWx3cZE00DQ1hc836Lj5HpctexN1PxU8Mu/BN5DzOXfYbf5s3QtCksXgx161o83pFgwl4Ohr0urh2rlaTDhqloFAOnOj/M0A9fkEBDCCEs8Pngo2lsJFFhQaSmGy94vXclADC/dmtu+ue9cA1pV02my11IzUzFpkr1WTdzEQ+++YKxEqZFC5g929gVNRe11SkabAeVdtvDA3zyCZrkZChVipiZ04gpJdUqQghhic8nnK7Yl8rN23oASmZczU40nV2/k8XjK5Uq5rax+SK1MxXF6tSGLVtQ2raFf/9F6dGD4yM+QK83mB2npjqlRDF/i63OTY3IJq48nKcaCu6W6CYkp8CePfDRR8Y7vvpKWvELIYQNPj3zkTuxsdv+9fgb9OyKqsrB0pUsPkYSTV3L3kxFzpybhH2pfNTmTQZcDqBPUgKVPnmfRSu3Ejj9GzrUj8l+jKk6JffMRUSwP/1aVGJQ26rZMx7WGpFZYur78snviXT45U00WVnGXXl79Sr4GyGEEEWYzwYflhIbH9m7GoDf49rmOV4STd3DNFMxYFZi9i6xJjlLVFfsS80OHN95aCCHSlXkvVXT6b5jGcue7MXyubPp0KBi9mPVdA211YjMGgV4cf7XaPbtg6go+O470EiOhxBC2OKzyy65ExurXDxJ3dQjZGl1LK7ZyuxY6cvgejmXOcKDA5j8lPX9TdrXijIPHDUaZjbqxssPv02mzo9Oh/4itNcT6DOumz3e1sZ2f+623YjMmu771vHUruUoGg3MmgVlyuTn5QshhE/xuZkPU8XCslwbxJlmPdbe25grxcLN7pO+DK5lLZkzd4lqo4ol2HniCuNXHLRYEbOianOef3QU0+Z/xH2HtpHWtj3hKxOMu+Ta8Ofuswya87fD465/9iCfLpsIwJmXh1D+wQcdPocQQvgijaKobBXpJunp6YSHh5OWlkZYWJhTz23pIgfG3h6bpjxHuWsXebnnCBKqt8i+b2SXmvxfi1iZ8XCR3Hk3JqZ325QIau1nZ0nj03v5/tcPCLt13ViKu2wZRFpeLjO1XndU+bRzzP/xdUpfv8rGGs1pmrSenafTpZeHEMJnOXL99pmZD2sXOYDmJ/dQ7tpF0gJDWF25KXA3x0MCD9ex1lAMzDfxMxhg4GzLPztLdpSvzVO9xzB/0Yf4b9sGbdoYd8XN1YxMbev13Mpcu8QPv4yi9PWr7CsTy5aPvuKNL9fbLsMVQgiRzSdyPmxd5AAeTTYuuSypeT+3/Pwlx8NN7DUUM+238u7CZNWBBxiDlkvV42DNWm6VLgO7d3MjvgX6Eycden5LotMvMG/OW1S5fJrU8NKs/nQ6k7eft12GK4QQwoxPBB+2LjLBt27S6eAmAH6vbVyzNyU2yqdW11Lb+vxyxi3V5zSFit3rRdPqz/O0f/gjToeVJvjYUS7XaciWHxc4/PwmNc//Q8L8d4m9ksLN8hWI3L6Zn89ideYG1O2UK4QQvsYngg9bF5kOhzcTknWT4xHRxD3WgTn9m7NxeFsJPNzAFT1TyoYF0qVuNN+sP0ZK2k1OlCjH430+5WCpCpS+donG//coB/7zHty+7dDzP3NoLUtmv0l46hmoXJmgTRvYqY1QNXOjZqdcIYTwJT4RfNi6yDxyZ8llfu02dKpTLk8JpnAdU0Mxa++2BogM8Vd1rkFtqjC0XTUUBZbsNl/qSAkrTc+nv2RBrQfwUwzU+GI0SuPGND291+bzA9Q8f4x1Kz/hw/mfo8u8CZ06wdatUKGC6pkTR2dYhBCiqPOJ4MPaRa7stYu0OLELgI3xHaWBmJvZan1u+v6jHnF2AwStBjJv65mw8hDnrmVaPOZGQBBDuv6HtzoM4mpQcTS7dqFr/QAJPw3l2Z2LqZtymNDMDMJu/kvlS6d4ctdyZv7yHn/+8BoVd26EgAAYPRqWLIGSxj1b1M6cSFdcIYQw5zOltn/uTuGV2eYllS9u/Z23185g+z21uJSwUpZaPMTepm22KpXyo8T1NOaf/ZNKi36BLBVNxZ58EsaMgXvvNbtZb1BoOW613VbwG4e3ldk0IUSRJ6W2uSQkpzB6ad6Sym771wNQ7LlnaKIy8LC3rbpwnL3W5x3jopn8VAMGzfkbZ+RuXikWTsq4iVSaOsG4G+7vv6McOIDm/HkAboeGoateDc3DD8Njj0G1amaPz/k70KtJBSasPGSzFbz8fgghhLkiH3xY+9Rc8cpZ6pw7ikGno/Zrz6s+l91t1UW+mFqfW1MiJNApgYfZHj1aDbz2Grz2mjFYuHYN/PzwCw62+nhLvwMRxYx5KTlbs0tXXCGEsK5IBx+2+nt0ObARgG331qdJZEl0ds5lLYgx9XOQ0lzXcmbSptXZCDtt2K39DqRdz0IBhrarSqVSITIjJoQQdhTphFNb/T26HtgAwB+VW9gthbTXiROkn4OrOSNpM7oA/VvUdGOdu/0UXetKxZQQQthTpIMPa5+W7710mlrnj5Gl1fG/as3tfqpW24lT+jm4jr2yXBNr9w9tV7VA/Vvkd0AIIZzHZcHH5MmTqVSpEkFBQTRr1oxt27a56qmssvZpufNB45LLpor1uRocZvdTtfRz8Dx7Zbka4KVWsUSFm/8so8ODmNq3IYPbVbM4G6E3KGw+eomFSWfYfPSS1dkr+R0QQgjncUnOx7x58xg2bBhTp06lWbNmTJgwgQ4dOnDw4EHKlCnjiqe0yPRpOXcppCnfY2mNlkSbkg9tkH4O3qFjXDRT+jbMk/CZM7nzzY41VVcjOZJALL8DQgjhPC7p89GsWTOaNGnCpEmTADAYDMTExPDqq6/y1ltv2Xyss/t8mJIEwTg1XvniKVZ9N4BbWj+avDqLcS+0sjsVL/0cvIszyp2tJY+azpI7N0R+B4QQwjZHrt9OX3a5desWO3fupF27dnefRKulXbt2bN68Oc/xmZmZpKenm305k+nTsmk63pRour1qI1WBB6jrxCn9HNzHVJbbo/49+UruzE8CsfwOCCGE8zg9+Lh48SJ6vZ6yZcua3V62bFlSU1PzHD927FjCw8Ozv2JiYpw9JDrGRbNxeFvm9G/O8yk7AIh/a4BDyYe5gxgT2QG38Mlv8qj8DgghhHN4vM/HiBEjGDZsWPb36enpLglAdFoN8TdT4Z9DEBCA0qMHm49ecmjq3l4nTlE4FCR5VH4HhBCi4JwefJQqVQqdTse5c+fMbj937hxRUVF5jg8MDCQwMNDZw7Dsl1+MY7mvNT2n7sxXp1J7nTiF9yto8qia3wFpwy+EENY5PfgICAigUaNGrFq1ip49ewLGhNNVq1YxaNAgZz+deoqSHXyMLV4nz7S7dCot3By52FurgjIxa8GeD9KGXwghbHPJssuwYcN49tlnady4MU2bNmXChAlkZGTQr18/VzydOnv2wIEDZPr5s7JKszx3m7pUfrB4H+1rRcmn1ELE3sXeUmAyqlstBsxKdPqGcNKGXwgh7HNJ8PHkk09y4cIF3nvvPVJTU6lfvz4JCQl5klDdKiyMs88NYNXfJ/g3sJjFQ3ImGsrSSuFg72L/YqtYFu1KsRiY2OsZ4ig1LdgluBVCCBf1+SgIZ/f5yGlh0hkGz02ye9zEXvXpUf8epz63cD5T7w1blSuW5Ozl4czk0c1HL9F7+ha7x83p31yCWyFEkePI9dvj1S7uJF0qixZ7JbPW5J6FcFYgIC3YhRBCnSK9sVxu9jYn04CqduvCOxTkIu6KjeAkuBVCCHV8KviQLpVFizMu4s6chZDgVggh1PGp4AOkS2VRYu9ir4YzZyEkuBVCCHV8KuE0J2kCVTTk3jhQLVduBCd9PoQQvsiR67fPBh+i6LB2se9eL5pp648Blnt5uHKmS4JbIYSvkWoX4VNs7bfSoEIJp/byUEva8AshhHUy8yGKPJmFEEII15OZDyFyKOgshAQvQgjhXBJ8CGGDJI8KIYTz+VyprRBqmSpprO2AnJCc4qGRCSFE4SbBhxAW2NskDozt2fUGr0qZEkKIQkGCDyEssLdvjCvaswshhK+Q4EMIC2STOCGEcB0JPoSwQDaJE0II15HgQwgLZJM4IYRwHQk+hLBANokTQgjXkeBDCCtkB2QhhHANaTImhA229o0RQgiRPxJ8CGGHbBInhBDOJcsuQgghhHArCT6EEEII4VYSfAghhBDCrST4EEIIIYRbSfAhhBBCCLeS4EMIIYQQbiXBhxBCCCHcSoIPIYQQQriVBB9CCCGEcCuv63CqKAoA6enpHh6JEEIIIdQyXbdN13FbvC74uHbtGgAxMTEeHokQQgghHHXt2jXCw8NtHqNR1IQobmQwGDh79iyhoaFoNM7dvCs9PZ2YmBhOnTpFWFiYU8/tjeT1Fm3yeos2X3u94Huvuai9XkVRuHbtGuXKlUOrtZ3V4XUzH1qtlvLly7v0OcLCworED1oteb1Fm7zeos3XXi/43msuSq/X3oyHiSScCiGEEMKtJPgQQgghhFv5VPARGBjIqFGjCAwM9PRQ3EJeb9Emr7do87XXC773mn3t9ebkdQmnQgghhCjafGrmQwghhBCeJ8GHEEIIIdxKgg8hhBBCuJUEH0IIIYRwK58JPiZPnkylSpUICgqiWbNmbNu2zdNDcpmxY8fSpEkTQkNDKVOmDD179uTgwYOeHpZbfPLJJ2g0GoYMGeLpobjUmTNn6Nu3LyVLliQ4OJg6deqwY8cOTw/LJfR6PSNHjiQ2Npbg4GAqV67M6NGjVe0fURisX7+ebt26Ua5cOTQaDQsWLDC7X1EU3nvvPaKjowkODqZdu3YcPnzYM4N1AluvNysri+HDh1OnTh1CQkIoV64czzzzDGfPnvXcgAvI3s83p5dffhmNRsOECRPcNj5P8YngY968eQwbNoxRo0aRmJhIvXr16NChA+fPn/f00Fxi3bp1DBw4kC1btrBixQqysrJ46KGHyMjI8PTQXGr79u1888031K1b19NDcakrV67QokUL/P39WbZsGfv27eOLL76gRIkSnh6aS4wbN44pU6YwadIk9u/fz7hx4/j000/5+uuvPT00p8jIyKBevXpMnjzZ4v2ffvopX331FVOnTmXr1q2EhITQoUMHbt686eaROoet13v9+nUSExMZOXIkiYmJ/PHHHxw8eJDu3bt7YKTOYe/nazJ//ny2bNlCuXLl3DQyD1N8QNOmTZWBAwdmf6/X65Vy5copY8eO9eCo3Of8+fMKoKxbt87TQ3GZa9euKVWrVlVWrFihPPDAA8rgwYM9PSSXGT58uNKyZUtPD8NtunTpojz33HNmtz3yyCNKnz59PDQi1wGU+fPnZ39vMBiUqKgo5bPPPsu+7erVq0pgYKAyZ84cD4zQuXK/Xku2bdumAMqJEyfcMygXsvZ6T58+rdxzzz1KcnKyUrFiRWX8+PFuH5u7FfmZj1u3brFz507atWuXfZtWq6Vdu3Zs3rzZgyNzn7S0NAAiIyM9PBLXGThwIF26dDH7ORdVixYtonHjxjz++OOUKVOGBg0aMH36dE8Py2Xuu+8+Vq1axaFDhwDYtWsXGzdupFOnTh4emesdO3aM1NRUs9/r8PBwmjVr5lN/vzQaDREREZ4eiksYDAaefvpp3njjDWrXru3p4biN120s52wXL15Er9dTtmxZs9vLli3LgQMHPDQq9zEYDAwZMoQWLVoQFxfn6eG4xNy5c0lMTGT79u2eHopb/PPPP0yZMoVhw4bx9ttvs337dl577TUCAgJ49tlnPT08p3vrrbdIT0+nRo0a6HQ69Ho9H3/8MX369PH00FwuNTUVwOLfL9N9RdnNmzcZPnw4vXv3LjIbr+U2btw4/Pz8eO211zw9FLcq8sGHrxs4cCDJycls3LjR00NxiVOnTjF48GBWrFhBUFCQp4fjFgaDgcaNGzNmzBgAGjRoQHJyMlOnTi2Swccvv/zCzz//zOzZs6lduzZJSUkMGTKEcuXKFcnXK4yysrJ44oknUBSFKVOmeHo4LrFz504mTpxIYmIiGo3G08NxqyK/7FKqVCl0Oh3nzp0zu/3cuXNERUV5aFTuMWjQIJYsWcKaNWsoX768p4fjEjt37uT8+fM0bNgQPz8//Pz8WLduHV999RV+fn7o9XpPD9HpoqOjqVWrltltNWvW5OTJkx4akWu98cYbvPXWW/Tq1Ys6derw9NNPM3ToUMaOHevpobmc6W+Ur/39MgUeJ06cYMWKFUV21mPDhg2cP3+eChUqZP/9OnHiBK+//jqVKlXy9PBcqsgHHwEBATRq1IhVq1Zl32YwGFi1ahXx8fEeHJnrKIrCoEGDmD9/PqtXryY2NtbTQ3KZBx98kD179pCUlJT91bhxY/r06UNSUhI6nc7TQ3S6Fi1a5CmdPnToEBUrVvTQiFzr+vXraLXmf6p0Oh0Gg8FDI3Kf2NhYoqKizP5+paens3Xr1iL798sUeBw+fJiVK1dSsmRJTw/JZZ5++ml2795t9verXLlyvPHGGyxfvtzTw3Mpn1h2GTZsGM8++yyNGzemadOmTJgwgYyMDPr16+fpobnEwIEDmT17NgsXLiQ0NDR7bTg8PJzg4GAPj865QkND8+SyhISEULJkySKb4zJ06FDuu+8+xowZwxNPPMG2bduYNm0a06ZN8/TQXKJbt258/PHHVKhQgdq1a/P333/z5Zdf8txzz3l6aE7x77//cuTIkezvjx07RlJSEpGRkVSoUIEhQ4bw0UcfUbVqVWJjYxk5ciTlypWjZ8+enht0Adh6vdHR0Tz22GMkJiayZMkS9Hp99t+vyMhIAgICPDXsfLP3880dXPn7+xMVFUX16tXdPVT38nS5jbt8/fXXSoUKFZSAgACladOmypYtWzw9JJcBLH7NmDHD00Nzi6JeaqsoirJ48WIlLi5OCQwMVGrUqKFMmzbN00NymfT0dGXw4MFKhQoVlKCgIOXee+9V3nnnHSUzM9PTQ3OKNWvWWPz/9dlnn1UUxVhuO3LkSKVs2bJKYGCg8uCDDyoHDx707KALwNbrPXbsmNW/X2vWrPH00PPF3s83N18ptdUoShFpEyiEEEKIQqHI53wIIYQQwrtI8CGEEEIIt5LgQwghhBBuJcGHEEIIIdxKgg8hhBBCuJUEH0IIIYRwKwk+hBBCCOFWEnwIIYQQwq0k+BBCCCGEW0nwIYQQQgi3kuBDCCGEEG4lwYcQQggh3Or/AWTwZXzRVPYBAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -433,7 +461,7 @@ " y_err = np.random.normal(y,0.5)\n", " return y + y_err + 0.5 * x\n", "\n", - "x_train = np.linspace(0,15,100)\n", + "x_train = np.linspace(0,15,200)\n", "y_train = noisy_1d(x_train)\n", "\n", "x_test = np.linspace(0,15,200)\n", @@ -444,12 +472,57 @@ "plt.show()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#| hide\n", + "### Unit Tests for Edge Cases" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Curve fitting with 4 kernels and polynomials of 2 degree requires at least 12 datapoints.\n", + "Number of kernels will be reduced to 2 kernels.\n" + ] + } + ], + "source": [ + "#| hide\n", + "\n", + "def noisy_1d(x):\n", + " y = np.sin(x)\n", + " y_err = np.random.normal(y,0.5)\n", + " return y + y_err + 0.5 * x\n", + "\n", + "# To few datapoints for choosen number of kernels\n", + "\n", + "x_train = np.linspace(0,15,6)\n", + "y_train = noisy_1d(x_train)\n", + "x_test = np.linspace(0,15,10)\n", + "y_test = LOESSRegression(n_kernels=4, polynomial_degree=2).fit(x_train, y_train).predict(x_test)\n", + "\n", + "# Extrapolation\n", + "\n", + "x_train = np.linspace(0,15,60)\n", + "y_train = noisy_1d(x_train)\n", + "x_test = np.linspace(-10,25,10)\n", + "y_test = LOESSRegression(n_kernels=4, polynomial_degree=2).fit(x_train, y_train).predict(x_test)\n", + "\n", + "# single datapoint inference\n", + "\n", + "x_train = np.linspace(0,15,100)\n", + "y_train = noisy_1d(x_train)\n", + "x_test = np.linspace(10,10,1)\n", + "y_test = LOESSRegression(n_kernels=4, polynomial_degree=2).fit(x_train, y_train).predict(x_test)" + ] } ], "metadata": { From 2f91919ba64650f5c803d40ccf4d3259438f1327 Mon Sep 17 00:00:00 2001 From: "Zeng, Wen-Feng" Date: Tue, 4 Oct 2022 11:35:17 +0200 Subject: [PATCH 06/52] sklearn in req --- requirements/requirements.txt | 3 ++- requirements/requirements_development.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index beaeccae..896ad272 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -7,4 +7,5 @@ contextlib2 mmh3 biopython psutil -tqdm \ No newline at end of file +tqdm +scikit-learn \ No newline at end of file diff --git a/requirements/requirements_development.txt b/requirements/requirements_development.txt index 22a41174..09674432 100644 --- a/requirements/requirements_development.txt +++ b/requirements/requirements_development.txt @@ -8,6 +8,7 @@ pipdeptree ipykernel nbdev pyteomics +scikit-learn tqdm psutil From a2621653ee7e959bc95e7f985519d1ceacec8181 Mon Sep 17 00:00:00 2001 From: "Zeng, Wen-Feng" Date: Mon, 10 Oct 2022 16:26:08 +0200 Subject: [PATCH 07/52] percolator --- alphabase/_modidx.py | 43 ++ alphabase/scoring/__init__.py | 0 alphabase/scoring/fdr.py | 169 +++++ alphabase/scoring/feature_extraction_base.py | 49 ++ alphabase/scoring/ml_scoring_base.py | 293 ++++++++ nbdev_nbs/psm_reader/dia_psm_reader.ipynb | 9 + nbdev_nbs/scoring/fdr.ipynb | 625 ++++++++++++++++++ .../scoring/feature_extraction_base.ipynb | 98 +++ nbdev_nbs/scoring/ml_scoring_base.ipynb | 445 +++++++++++++ 9 files changed, 1731 insertions(+) create mode 100644 alphabase/scoring/__init__.py create mode 100644 alphabase/scoring/fdr.py create mode 100644 alphabase/scoring/feature_extraction_base.py create mode 100644 alphabase/scoring/ml_scoring_base.py create mode 100644 nbdev_nbs/scoring/fdr.ipynb create mode 100644 nbdev_nbs/scoring/feature_extraction_base.ipynb create mode 100644 nbdev_nbs/scoring/ml_scoring_base.ipynb diff --git a/alphabase/_modidx.py b/alphabase/_modidx.py index bd864a2f..27d6f5bc 100644 --- a/alphabase/_modidx.py +++ b/alphabase/_modidx.py @@ -387,6 +387,49 @@ 'alphabase/psm_reader/psm_reader.py'), 'alphabase.psm_reader.psm_reader.translate_other_modification': ( 'psm_reader/psm_reader.html#translate_other_modification', 'alphabase/psm_reader/psm_reader.py')}, + 'alphabase.scoring.fdr': { 'alphabase.scoring.fdr.calculate_fdr': ( 'scoring/fdr.html#calculate_fdr', + 'alphabase/scoring/fdr.py'), + 'alphabase.scoring.fdr.calculate_fdr_from_ref': ( 'scoring/fdr.html#calculate_fdr_from_ref', + 'alphabase/scoring/fdr.py'), + 'alphabase.scoring.fdr.fdr_from_ref': ('scoring/fdr.html#fdr_from_ref', 'alphabase/scoring/fdr.py'), + 'alphabase.scoring.fdr.fdr_to_q_values': ( 'scoring/fdr.html#fdr_to_q_values', + 'alphabase/scoring/fdr.py')}, + 'alphabase.scoring.feature_extraction_base': { 'alphabase.scoring.feature_extraction_base.BaseFeatureExtractor': ( 'scoring/feature_extraction_base.html#basefeatureextractor', + 'alphabase/scoring/feature_extraction_base.py'), + 'alphabase.scoring.feature_extraction_base.BaseFeatureExtractor.__init__': ( 'scoring/feature_extraction_base.html#basefeatureextractor.__init__', + 'alphabase/scoring/feature_extraction_base.py'), + 'alphabase.scoring.feature_extraction_base.BaseFeatureExtractor.extract_features': ( 'scoring/feature_extraction_base.html#basefeatureextractor.extract_features', + 'alphabase/scoring/feature_extraction_base.py'), + 'alphabase.scoring.feature_extraction_base.BaseFeatureExtractor.feature_list': ( 'scoring/feature_extraction_base.html#basefeatureextractor.feature_list', + 'alphabase/scoring/feature_extraction_base.py')}, + 'alphabase.scoring.ml_scoring_base': { 'alphabase.scoring.ml_scoring_base.Percolator': ( 'scoring/ml_scoring_base.html#percolator', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator.__init__': ( 'scoring/ml_scoring_base.html#percolator.__init__', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator._cv_score': ( 'scoring/ml_scoring_base.html#percolator._cv_score', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator._estimate_fdr': ( 'scoring/ml_scoring_base.html#percolator._estimate_fdr', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator._estimate_fdr_per_raw': ( 'scoring/ml_scoring_base.html#percolator._estimate_fdr_per_raw', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator._estimate_psm_fdr': ( 'scoring/ml_scoring_base.html#percolator._estimate_psm_fdr', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator._predict': ( 'scoring/ml_scoring_base.html#percolator._predict', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator._train': ( 'scoring/ml_scoring_base.html#percolator._train', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator.extract_features': ( 'scoring/ml_scoring_base.html#percolator.extract_features', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator.feature_extractor': ( 'scoring/ml_scoring_base.html#percolator.feature_extractor', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator.feature_list': ( 'scoring/ml_scoring_base.html#percolator.feature_list', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator.ml_model': ( 'scoring/ml_scoring_base.html#percolator.ml_model', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator.rescore': ( 'scoring/ml_scoring_base.html#percolator.rescore', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator.run': ( 'scoring/ml_scoring_base.html#percolator.run', + 'alphabase/scoring/ml_scoring_base.py')}, 'alphabase.spectral_library.decoy_library': { 'alphabase.spectral_library.decoy_library.DecoyLib': ( 'spectral_library/decoy_library.html#decoylib', 'alphabase/spectral_library/decoy_library.py'), 'alphabase.spectral_library.decoy_library.DecoyLib.__init__': ( 'spectral_library/decoy_library.html#decoylib.__init__', diff --git a/alphabase/scoring/__init__.py b/alphabase/scoring/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/alphabase/scoring/fdr.py b/alphabase/scoring/fdr.py new file mode 100644 index 00000000..caf13781 --- /dev/null +++ b/alphabase/scoring/fdr.py @@ -0,0 +1,169 @@ +# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbdev_nbs/scoring/fdr.ipynb. + +# %% auto 0 +__all__ = ['calc_fdr_for_df', 'calc_fdr_from_ref_for_df', 'fdr_to_q_values', 'calculate_fdr', 'fdr_from_ref', + 'calculate_fdr_from_ref'] + +# %% ../../nbdev_nbs/scoring/fdr.ipynb 3 +import numba +import numpy as np +import pandas as pd + +# %% ../../nbdev_nbs/scoring/fdr.ipynb 4 +@numba.njit +def fdr_to_q_values( + fdr_values:np.ndarray +)->np.ndarray: + """convert FDR values to q_values. + + Parameters + ---------- + fdr_values : np.ndarray + FDR values, they should be + sorted according to the descending order of the `score` + + Returns + ------- + np.ndarray + q_values + + """ + q_values = np.zeros_like(fdr_values) + min_q_value = np.max(fdr_values) + for i in range(len(fdr_values) - 1, -1, -1): + fdr = fdr_values[i] + if fdr < min_q_value: + min_q_value = fdr + q_values[i] = min_q_value + return q_values + +def calculate_fdr( + df:pd.DataFrame, + score_column:str, + decoy_column:str='decoy' +)->pd.DataFrame: + """Calculate FDR values (q_values in fact) for the given dataframe + + Parameters + ---------- + df : pd.DataFrame + PSM dataframe to calculate FDRs + + score_column : str + score column to sort in decending order + + decoy_column : str, optional + decoy column in the dataframe. + 1=target, 0=decoy. Defaults to 'decoy'. + + Returns + ------- + pd.DataFrame + PSM dataframe with 'fdr' column added + + """ + df = df.reset_index(drop=True).sort_values( + [score_column,decoy_column], ascending=False + ) + target_values = 1-df[decoy_column].values + decoy_cumsum = np.cumsum(df[decoy_column].values) + target_cumsum = np.cumsum(target_values) + fdr_values = decoy_cumsum/target_cumsum + df['fdr'] = fdr_to_q_values(fdr_values) + return df + +#wrapper +calc_fdr_for_df = calculate_fdr + +@numba.njit +def fdr_from_ref( + sorted_scores:np.ndarray, + ref_scores:np.ndarray, + ref_fdr_values:np.ndarray +)->np.ndarray: + """ Calculate FDR values from the given reference scores and fdr_values. + It is used to extend peptide-level or sequence-level FDR (reference) + to each PSM, as PSMs are more useful for quantification. + + Parameters + ---------- + sorted_scores : np.array + the scores to calculate FDRs, + they must be sorted in decending order. + + ref_scores : np.array + reference scores that used to + calculate ref_fdr_values, also sorted in decending order. + + ref_fdr_values : np.array + fdr values corresponding to ref_scores + + Returns + ------- + np.array + fdr values corresponding to sorted_scores. + + """ + q_values = np.zeros_like(sorted_scores) + i,j = 0,0 + while i < len(sorted_scores) and j < len(ref_scores): + if sorted_scores[i] >= ref_scores[j]: + q_values[i] = ref_fdr_values[j] + i += 1 + else: + j += 1 + while i < len(sorted_scores): + q_values[i] = ref_fdr_values[-1] + i += 1 + return q_values + +def calculate_fdr_from_ref( + df: pd.DataFrame, + ref_scores:np.ndarray, + ref_fdr_values:np.ndarray, + score_column:str, + decoy_column:str='decoy' +)->pd.DataFrame: + """ Calculate FDR values for a PSM dataframe from the given reference + scores and fdr_values. It is used to extend peptide-level or + sequence-level FDR (reference) to each PSM, as PSMs are more useful + for quantification. + `` + + Parameters + ---------- + df : pd.DataFrame + PSM dataframe + + ref_scores : np.array + reference scores that used to + calculate ref_fdr_values, also sorted in decending order. + + ref_fdr_values : np.array + fdr values corresponding to ref_scores + + score_column : str + score column in the dataframe + + decoy_column : str, optional + decoy column in the dataframe. + 1=target, 0=decoy. Defaults to 'decoy'. + + Returns + ------- + pd.DataFrame + dataframe with 'fdr' column added + + """ + df = df.reset_index(drop=True).sort_values( + [score_column,decoy_column], ascending=False + ) + sorted_idxes = np.argsort(ref_fdr_values) + ref_scores = ref_scores[sorted_idxes] + ref_q_values = ref_fdr_values[sorted_idxes] + df['fdr'] = fdr_from_ref( + df.score.values, ref_scores, ref_q_values + ) + return df + +calc_fdr_from_ref_for_df = calculate_fdr_from_ref diff --git a/alphabase/scoring/feature_extraction_base.py b/alphabase/scoring/feature_extraction_base.py new file mode 100644 index 00000000..02778b8f --- /dev/null +++ b/alphabase/scoring/feature_extraction_base.py @@ -0,0 +1,49 @@ +# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbdev_nbs/scoring/feature_extraction_base.ipynb. + +# %% auto 0 +__all__ = ['BaseFeatureExtractor'] + +# %% ../../nbdev_nbs/scoring/feature_extraction_base.ipynb 2 +import pandas as pd + +# %% ../../nbdev_nbs/scoring/feature_extraction_base.ipynb 3 +class BaseFeatureExtractor: + def __init__(self): + self._feature_list = ['score','nAA','charge'] + + @property + def feature_list(self)->list: + """ + This is a property. It tells ML scoring modules + what features (columns) are extracted by + this FeatureExtractor for scoring. + + Returns + ------- + list + feature names (columns) in the PSM dataframe + """ + + self._feature_list = list(set(self._feature_list)) + return self._feature_list + + def extract_features(self, + psm_df:pd.DataFrame, + *args, **kwargs + )->pd.DataFrame: + """ + Extract the scoring features (self._feature_list) + and append them inplace into candidate PSMs (psm_df). + + Parameters + ---------- + psm_df : pd.DataFrame + PSMs to be rescore. + + Returns + ------- + pd.DataFrame + psm_df with appended the feature list extracted by this extractor. + """ + return psm_df + diff --git a/alphabase/scoring/ml_scoring_base.py b/alphabase/scoring/ml_scoring_base.py new file mode 100644 index 00000000..7f146a56 --- /dev/null +++ b/alphabase/scoring/ml_scoring_base.py @@ -0,0 +1,293 @@ +# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbdev_nbs/scoring/ml_scoring_base.ipynb. + +# %% auto 0 +__all__ = ['Percolator'] + +# %% ../../nbdev_nbs/scoring/ml_scoring_base.ipynb 2 +import pandas as pd +import numpy as np + +from sklearn.linear_model import LogisticRegression +from sklearn.base import BaseEstimator + +from .feature_extraction_base import BaseFeatureExtractor +from alphabase.scoring.fdr import ( + calculate_fdr, + calculate_fdr_from_ref, + fdr_to_q_values, + fdr_from_ref, +) + +# %% ../../nbdev_nbs/scoring/ml_scoring_base.ipynb 6 +class Percolator: + def __init__(self): + self._feature_extractor:BaseFeatureExtractor = BaseFeatureExtractor() + self._ml_model = LogisticRegression() + + self.fdr_level = 'psm' # psm, precursor, peptide, or sequence + self.fdr = 0.01 + self.per_raw_fdr = False + + self.max_training_sample = 200000 + self.min_training_sample = 100 + self.cv_fold = 1 + self.iter_num = 1 + + @property + def feature_list(self)->list: + """ The read-only property to get extracted feature_list """ + return self.feature_extractor.feature_list + + @property + def ml_model(self): + return self._ml_model + + @ml_model.setter + def ml_model(self, model): + """ + `model` must be sklearn models or other models but implement + the same methods `fit()` and `decision_function()`/`predict_proba()` + as sklearn models + """ + self._ml_model = model + + @property + def feature_extractor(self)->BaseFeatureExtractor: + return self._feature_extractor + + @feature_extractor.setter + def feature_extractor(self, fe:BaseFeatureExtractor): + self._feature_extractor = fe + + def extract_features(self, + psm_df:pd.DataFrame, + *args, **kwargs + )->pd.DataFrame: + """ + Extract features for rescoring. + + *args and **kwargs are used for + `self.feature_extractor.extract_features`. + + Parameters + ---------- + psm_df : pd.DataFrame + PSM DataFrame + + Returns + ------- + pd.DataFrame + psm_df with feature columns appended inplace. + """ + psm_df['ml_score'] = psm_df.score + psm_df = self._estimate_psm_fdr(psm_df) + return self._feature_extractor.extract_features( + psm_df, *args, **kwargs + ) + + def rescore(self, + df:pd.DataFrame + )->pd.DataFrame: + """Rescore + + Parameters + ---------- + df : pd.DataFrame + psm_df + + Returns + ------- + pd.DataFrame + psm_df with `ml_score` and `fdr` columns updated inplace + """ + for i in range(self.iter_num): + df = self._cv_score(df) + df = self._estimate_fdr(df, 'psm', False) + df = self._estimate_fdr(df) + return df + + def run(self, + psm_df:pd.DataFrame, + *args, **kwargs + )->pd.DataFrame: + """ + Run percolator workflow: + + - self.extract_features() + - self.re_score() + + *args and **kwargs are used for + `self.feature_extractor.extract_features`. + + Parameters + ---------- + psm_df : pd.DataFrame + PSM DataFrame + + Returns + ------- + pd.DataFrame + psm_df with feature columns appended inplace. + """ + df = self.extract_features( + psm_df, *args, **kwargs + ) + return self.rescore(df) + + def _estimate_fdr_per_raw(self, + df:pd.DataFrame, + fdr_level:str + )->pd.DataFrame: + df_list = [] + for raw_name, df_raw in df.groupby('raw_name'): + df_list.append(self._estimate_fdr(df_raw, + fdr_level = fdr_level, + per_raw_fdr = False + )) + return pd.concat(df_list, ignore_index=True) + + def _estimate_psm_fdr(self, + df:pd.DataFrame, + )->pd.DataFrame: + df = df.sort_values( + ['ml_score','decoy'], ascending=False + ).reset_index(drop=True) + target_values = 1-df['decoy'].values + decoy_cumsum = np.cumsum(df['decoy'].values) + target_cumsum = np.cumsum(target_values) + fdr_values = decoy_cumsum/target_cumsum + df['fdr'] = fdr_to_q_values(fdr_values) + return df + + def _estimate_fdr(self, + df:pd.DataFrame, + fdr_level:str=None, + per_raw_fdr:bool=None, + )->pd.DataFrame: + if fdr_level is None: + fdr_level = self.fdr_level + if per_raw_fdr is None: + per_raw_fdr = self.per_raw_fdr + + if per_raw_fdr: + return self._estimate_fdr_per_raw( + df, fdr_level=fdr_level + ) + + if fdr_level == 'psm': + return self._estimate_psm_fdr(df) + else: + if fdr_level == 'precursor': + _df = df.groupby([ + 'sequence','mods','mod_sites','charge','decoy' + ])['ml_score'].max() + elif fdr_level == 'peptide': + _df = df.groupby([ + 'sequence','mods','mod_sites','decoy' + ])['ml_score'].max() + else: + _df = df.groupby(['sequence','decoy'])['ml_score'].max() + _df = self._estimate_psm_fdr(_df) + df['fdr'] = fdr_from_ref( + df['ml_score'].values, _df['ml_score'].values, + _df['fdr'].values + ) + return df + + def _train(self, + train_t_df:pd.DataFrame, + train_d_df:pd.DataFrame + ): + train_t_df = train_t_df[train_t_df.fdr<=self.fdr] + + if len(train_t_df) > self.max_train_sample: + train_t_df = train_t_df.sample( + n=self.max_training_sample, + random_state=1337 + ) + if len(train_d_df) > self.max_train_sample: + train_d_df = train_d_df.sample( + n=self.max_training_sample, + random_state=1337 + ) + + train_df = pd.concat((train_t_df, train_d_df)) + train_label = np.ones(len(train_df),dtype=np.int32) + train_label[len(train_t_df):] = 0 + + self._ml_model.fit( + train_df[self.feature_list].values, + train_label + ) + + def _predict(self, test_df): + try: + test_df['ml_score'] = self._ml_model.decision_function( + test_df[self.feature_list].values + ) + except AttributeError: + test_df['ml_score'] = self._ml_model.predict_proba( + test_df[self.feature_list].values + ) + return test_df + + def _cv_score(self, df:pd.DataFrame)->pd.DataFrame: + """ + Apply cross-validation for rescoring. + + It will split `df` into K folds. For each fold, + its ML scores are predicted by a model which + is trained by other K-1 folds . + + Parameters + ---------- + df : pd.DataFrame + PSMs to be rescored + + Returns + ------- + pd.DataFrame + PSMs after rescoring + """ + df = df.sample( + frac=1, random_state=1337 + ).reset_index(drop=True) + df_target = df[df.decoy == 0] + df_decoy = df[df.decoy != 0] + + if ( + np.sum(df_target.fdr 1: + test_df_list = [] + for i in range(self.cv_fold): + t_mask = np.ones(len(df_target), dtype=bool) + _slice = slice(i, len(df_target), self.cv_fold) + t_mask[_slice] = False + train_t_df = df_target[t_mask] + test_t_df = df_target[_slice] + + d_mask = np.ones(len(df_decoy), dtype=bool) + _slice = slice(i, len(df_decoy), self.cv_fold) + d_mask[_slice] = False + train_d_df = df_decoy[d_mask] + test_d_df = df_decoy[_slice] + + self._train(train_t_df, train_d_df) + + test_df = pd.concat((test_t_df, test_d_df)) + test_df_list.append(self._predict(test_df)) + + return pd.concat(test_df_list, ignore_index=True) + else: + + self._train(df_target, df_decoy) + test_df = pd.concat((df_target, df_decoy),ignore_index=True) + + return self._predict(test_df) + diff --git a/nbdev_nbs/psm_reader/dia_psm_reader.ipynb b/nbdev_nbs/psm_reader/dia_psm_reader.ipynb index bca7cc76..94a6003e 100644 --- a/nbdev_nbs/psm_reader/dia_psm_reader.ipynb +++ b/nbdev_nbs/psm_reader/dia_psm_reader.ipynb @@ -1219,6 +1219,15 @@ "display_name": "Python 3.8.3 ('base')", "language": "python", "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.8.3" + }, + "vscode": { + "interpreter": { + "hash": "8a3b27e141e49c996c9b863f8707e97aabd49c4a7e8445b9b783b34e4a21a9b2" + } } }, "nbformat": 4, diff --git a/nbdev_nbs/scoring/fdr.ipynb b/nbdev_nbs/scoring/fdr.ipynb new file mode 100644 index 00000000..993a76ba --- /dev/null +++ b/nbdev_nbs/scoring/fdr.ipynb @@ -0,0 +1,625 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "#| default_exp scoring.fdr" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# FDR" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Functionalities to calculate FDR.\n", + "\n", + "> In alphabase dataframes, we refer fdr values as q_values without loss of generacity." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "import numba\n", + "import numpy as np\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "\n", + "@numba.njit\n", + "def fdr_to_q_values(\n", + " fdr_values:np.ndarray\n", + ")->np.ndarray:\n", + " \"\"\"convert FDR values to q_values.\n", + "\n", + " Parameters\n", + " ----------\n", + " fdr_values : np.ndarray\n", + " FDR values, they should be \n", + " sorted according to the descending order of the `score`\n", + "\n", + " Returns\n", + " -------\n", + " np.ndarray\n", + " q_values\n", + "\n", + " \"\"\"\n", + " q_values = np.zeros_like(fdr_values)\n", + " min_q_value = np.max(fdr_values)\n", + " for i in range(len(fdr_values) - 1, -1, -1):\n", + " fdr = fdr_values[i]\n", + " if fdr < min_q_value:\n", + " min_q_value = fdr\n", + " q_values[i] = min_q_value\n", + " return q_values\n", + "\n", + "def calculate_fdr(\n", + " df:pd.DataFrame, \n", + " score_column:str, \n", + " decoy_column:str='decoy'\n", + ")->pd.DataFrame:\n", + " \"\"\"Calculate FDR values (q_values in fact) for the given dataframe\n", + "\n", + " Parameters\n", + " ----------\n", + " df : pd.DataFrame\n", + " PSM dataframe to calculate FDRs\n", + "\n", + " score_column : str\n", + " score column to sort in decending order\n", + "\n", + " decoy_column : str, optional\n", + " decoy column in the dataframe. \n", + " 1=target, 0=decoy. Defaults to 'decoy'.\n", + "\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " PSM dataframe with 'fdr' column added\n", + "\n", + " \"\"\"\n", + " df = df.reset_index(drop=True).sort_values(\n", + " [score_column,decoy_column], ascending=False\n", + " )\n", + " target_values = 1-df[decoy_column].values\n", + " decoy_cumsum = np.cumsum(df[decoy_column].values)\n", + " target_cumsum = np.cumsum(target_values)\n", + " fdr_values = decoy_cumsum/target_cumsum\n", + " df['fdr'] = fdr_to_q_values(fdr_values)\n", + " return df\n", + "\n", + "#wrapper\n", + "calc_fdr_for_df = calculate_fdr\n", + "\n", + "@numba.njit\n", + "def fdr_from_ref(\n", + " sorted_scores:np.ndarray, \n", + " ref_scores:np.ndarray, \n", + " ref_fdr_values:np.ndarray\n", + ")->np.ndarray:\n", + " \"\"\" Calculate FDR values from the given reference scores and fdr_values. \n", + " It is used to extend peptide-level or sequence-level FDR (reference) \n", + " to each PSM, as PSMs are more useful for quantification.\n", + "\n", + " Parameters\n", + " ----------\n", + " sorted_scores : np.array\n", + " the scores to calculate FDRs, \n", + " they must be sorted in decending order.\n", + "\n", + " ref_scores : np.array\n", + " reference scores that used to \n", + " calculate ref_fdr_values, also sorted in decending order.\n", + "\n", + " ref_fdr_values : np.array\n", + " fdr values corresponding to ref_scores\n", + "\n", + " Returns\n", + " -------\n", + " np.array\n", + " fdr values corresponding to sorted_scores.\n", + "\n", + " \"\"\"\n", + " q_values = np.zeros_like(sorted_scores)\n", + " i,j = 0,0\n", + " while i < len(sorted_scores) and j < len(ref_scores):\n", + " if sorted_scores[i] >= ref_scores[j]:\n", + " q_values[i] = ref_fdr_values[j]\n", + " i += 1\n", + " else:\n", + " j += 1\n", + " while i < len(sorted_scores):\n", + " q_values[i] = ref_fdr_values[-1]\n", + " i += 1\n", + " return q_values\n", + "\n", + "def calculate_fdr_from_ref(\n", + " df: pd.DataFrame,\n", + " ref_scores:np.ndarray, \n", + " ref_fdr_values:np.ndarray,\n", + " score_column:str, \n", + " decoy_column:str='decoy'\n", + ")->pd.DataFrame:\n", + " \"\"\" Calculate FDR values for a PSM dataframe from the given reference\n", + " scores and fdr_values. It is used to extend peptide-level or \n", + " sequence-level FDR (reference) to each PSM, as PSMs are more useful \n", + " for quantification.\n", + " ``\n", + "\n", + " Parameters\n", + " ----------\n", + " df : pd.DataFrame\n", + " PSM dataframe\n", + "\n", + " ref_scores : np.array\n", + " reference scores that used to \n", + " calculate ref_fdr_values, also sorted in decending order.\n", + "\n", + " ref_fdr_values : np.array\n", + " fdr values corresponding to ref_scores\n", + "\n", + " score_column : str\n", + " score column in the dataframe\n", + "\n", + " decoy_column : str, optional\n", + " decoy column in the dataframe. \n", + " 1=target, 0=decoy. Defaults to 'decoy'.\n", + "\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " dataframe with 'fdr' column added\n", + "\n", + " \"\"\"\n", + " df = df.reset_index(drop=True).sort_values(\n", + " [score_column,decoy_column], ascending=False\n", + " )\n", + " sorted_idxes = np.argsort(ref_fdr_values)\n", + " ref_scores = ref_scores[sorted_idxes]\n", + " ref_q_values = ref_fdr_values[sorted_idxes]\n", + " df['fdr'] = fdr_from_ref(\n", + " df.score.values, ref_scores, ref_q_values\n", + " )\n", + " return df\n", + "\n", + "calc_fdr_from_ref_for_df = calculate_fdr_from_ref" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
scoredecoykindfdr
41720.9867510True0.000000
48620.9588030True0.000000
4620.9542440True0.000000
13120.8324400True0.000000
23620.8095950True0.000000
...............
11110.0463660False0.504008
7090.0408411False0.504505
12090.0308410False0.504505
9390.0137041False0.505000
14390.0037040False0.505000
\n", + "

1505 rows × 4 columns

\n", + "
" + ], + "text/plain": [ + " score decoy kind fdr\n", + "417 20.986751 0 True 0.000000\n", + "486 20.958803 0 True 0.000000\n", + "46 20.954244 0 True 0.000000\n", + "131 20.832440 0 True 0.000000\n", + "236 20.809595 0 True 0.000000\n", + "... ... ... ... ...\n", + "1111 0.046366 0 False 0.504008\n", + "709 0.040841 1 False 0.504505\n", + "1209 0.030841 0 False 0.504505\n", + "939 0.013704 1 False 0.505000\n", + "1439 0.003704 0 False 0.505000\n", + "\n", + "[1505 rows x 4 columns]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = pd.DataFrame(\n", + " {\n", + " 'score': np.random.random(500)*10+11,\n", + " 'decoy': 0,\n", + " 'kind': True,\n", + " }\n", + ")\n", + "f_score = np.random.random(500)*9.9\n", + "df = df.append(\n", + " pd.DataFrame(\n", + " {\n", + " 'score': f_score+0.01,\n", + " 'decoy': 1,\n", + " 'kind': False\n", + " }\n", + " )\n", + ")\n", + "df = df.append(\n", + " pd.DataFrame(\n", + " {\n", + " 'score': f_score,\n", + " 'decoy': 0,\n", + " 'kind': False\n", + " }\n", + " )\n", + ")\n", + "df = df.append(\n", + " pd.DataFrame(\n", + " {\n", + " 'score': np.random.random(5)+10,\n", + " 'decoy': 1,\n", + " 'kind': False\n", + " }\n", + " )\n", + ")\n", + "\n", + "df = calculate_fdr(df, 'score', 'decoy')\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
scoredecoykindfdr
41720.9867510True0.0
48620.9588030True0.0
4620.9542440True0.0
13120.8324400True0.0
23620.8095950True0.0
...............
31311.0706950True0.0
22711.0284310True0.0
15311.0143300True0.0
11311.0139780True0.0
4811.0106290True0.0
\n", + "

500 rows × 4 columns

\n", + "
" + ], + "text/plain": [ + " score decoy kind fdr\n", + "417 20.986751 0 True 0.0\n", + "486 20.958803 0 True 0.0\n", + "46 20.954244 0 True 0.0\n", + "131 20.832440 0 True 0.0\n", + "236 20.809595 0 True 0.0\n", + ".. ... ... ... ...\n", + "313 11.070695 0 True 0.0\n", + "227 11.028431 0 True 0.0\n", + "153 11.014330 0 True 0.0\n", + "113 11.013978 0 True 0.0\n", + "48 11.010629 0 True 0.0\n", + "\n", + "[500 rows x 4 columns]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df[(df.fdr < 0.01)&(df.decoy==0)]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "#| hide\n", + "assert len(df[(df.fdr < 0.01)&(df.decoy==0)]) == 500" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "#| hide\n", + "dff = pd.DataFrame(\n", + " {\n", + " 'score': np.random.random(500)*10+11,\n", + " 'decoy': 0\n", + " }\n", + ")\n", + "f_score = np.random.random(500)*9.9\n", + "dff = dff.append(\n", + " pd.DataFrame(\n", + " {\n", + " 'score': f_score+0.01,\n", + " 'decoy': 1\n", + " }\n", + " )\n", + ")\n", + "dff = dff.append(\n", + " pd.DataFrame(\n", + " {\n", + " 'score': f_score,\n", + " 'decoy': 0\n", + " }\n", + " )\n", + ")\n", + "dff = dff.append(\n", + " pd.DataFrame(\n", + " {\n", + " 'score': np.random.random(5)+10,\n", + " 'decoy': 1\n", + " }\n", + " )\n", + ")\n", + "\n", + "dff['fdr'] = fdr_from_ref(dff.score.values, df.score.values, df.fdr.values)\n", + "\n", + "assert len(dff[(dff.fdr < 0.01)&(dff.decoy==0)]) == 500" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "#| hide\n", + "dff = calculate_fdr_from_ref(dff, df.score.values, df.fdr.values, 'score')\n", + "assert len(dff[(dff.fdr < 0.01)&(dff.decoy==0)]) == 500" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.3 ('base')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.3" + }, + "vscode": { + "interpreter": { + "hash": "8a3b27e141e49c996c9b863f8707e97aabd49c4a7e8445b9b783b34e4a21a9b2" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/nbdev_nbs/scoring/feature_extraction_base.ipynb b/nbdev_nbs/scoring/feature_extraction_base.ipynb new file mode 100644 index 00000000..258ce3f1 --- /dev/null +++ b/nbdev_nbs/scoring/feature_extraction_base.ipynb @@ -0,0 +1,98 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| default_exp scoring.feature_extraction_base" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Base Class of Feature Extractors" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "\n", + "class BaseFeatureExtractor:\n", + " def __init__(self):\n", + " self._feature_list = ['score','nAA','charge']\n", + "\n", + " @property\n", + " def feature_list(self)->list:\n", + " \"\"\"\n", + " This is a property. It tells ML scoring modules \n", + " what features (columns) are extracted by \n", + " this FeatureExtractor for scoring.\n", + "\n", + " Returns\n", + " -------\n", + " list\n", + " feature names (columns) in the PSM dataframe\n", + " \"\"\"\n", + "\n", + " self._feature_list = list(set(self._feature_list))\n", + " return self._feature_list\n", + "\n", + " def extract_features(self, \n", + " psm_df:pd.DataFrame, \n", + " *args, **kwargs\n", + " )->pd.DataFrame:\n", + " \"\"\"\n", + " Extract the scoring features (self._feature_list) \n", + " and append them inplace into candidate PSMs (psm_df).\n", + "\n", + " Parameters\n", + " ----------\n", + " psm_df : pd.DataFrame\n", + " PSMs to be rescore.\n", + "\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " psm_df with appended the feature list extracted by this extractor.\n", + " \"\"\"\n", + " return psm_df\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.3 ('base')", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.8.3" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "8a3b27e141e49c996c9b863f8707e97aabd49c4a7e8445b9b783b34e4a21a9b2" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/nbdev_nbs/scoring/ml_scoring_base.ipynb b/nbdev_nbs/scoring/ml_scoring_base.ipynb new file mode 100644 index 00000000..c0238437 --- /dev/null +++ b/nbdev_nbs/scoring/ml_scoring_base.ipynb @@ -0,0 +1,445 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| default_exp scoring.ml_scoring_base" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Base Class of ML Scoring Methods" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "from sklearn.linear_model import LogisticRegression\n", + "from sklearn.base import BaseEstimator\n", + "\n", + "from alphabase.scoring.feature_extraction_base import BaseFeatureExtractor\n", + "from alphabase.scoring.fdr import (\n", + " calculate_fdr,\n", + " calculate_fdr_from_ref,\n", + " fdr_to_q_values,\n", + " fdr_from_ref,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are two key modules in ML-based rescoring: feature extraction and rescoring algorithm. Here we designed these two modules as flexible as possible for future extensions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Feature extraction\n", + "\n", + "The feature extractor is more important than the ML methods, so we designed a flexible architecture for feature extraction. As shown in `BaseFeatureExtractor`, a feature extractor inherited from `BaseFeatureExtractor` must re-implement `BaseFeatureExtractor.extract_features`, and tells the ML methods what are the extracted features by providing `BaseFeatureExtractor.feature_list`. \n", + "\n", + "For example, if we have two feature extractors, `AlphaPeptFE` and `AlphaPeptDeepFE`:\n", + "\n", + "```python\n", + "class AlphaPeptFE(BaseFeatureExtractor):\n", + " def extract_features(self, psm_df):\n", + " psm_df['ap_f1'] = ...\n", + " self._feature_list.append('ap_f1')\n", + " psm_df['ap_f2'] = ...\n", + " self._feature_list.append('ap_f2')\n", + "\n", + "class AlphaPeptDeepFE(BaseFeatureExtractor):\n", + " def extract_features(self, psm_df):\n", + " psm_df['ad_f1'] = ...\n", + " self._feature_list.append('ad_f1')\n", + " psm_df['ad_f2'] = ...\n", + " self._feature_list.append('ad_f2')\n", + "```\n", + "\n", + "We can easily design a new feature extractor which combines these two and more feature extractors:\n", + "\n", + "```python\n", + "class CombFE(BaseFeatureExtractor):\n", + " def __init__(self):\n", + " self.fe_list = [AlphaPeptFE(),AlphaPeptDeepFE()]\n", + "\n", + " def extract_features(self, psm_df):\n", + " for fe in self.fe_list:\n", + " fe.extract_features(psm_df)\n", + "\n", + " @property\n", + " def feature_list(self):\n", + " f_set = set()\n", + " for fe in self.fe_list:\n", + " f_set.update(fe.feature_list)\n", + " return list(f_set)\n", + "```\n", + "\n", + "This will be useful for rescoring with DL features, for instance, when AlphaPeptDeep is or is not installed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rescoring Algorithm\n", + "\n", + "The rescoring algorithm called `Percolator` (Kall et al. 2007) based on the semi-supervised learning algorithm is still the most widely used in MS-based proteomics. Therefore, we used `Percolator` as the base rescoring class and others can re-implement its methods for different algorithms. as well as different \n", + "\n", + "1. Rescoring algorithm. We have provided the base rescoring code structure in `Percolator`. If we are going to support DiaNN's brute-force supervised learning methods, we can define the class like this:\n", + "\n", + "```python\n", + "class DiaNNRescoring(Percolator):\n", + " def _train(self, train_t_df, train_d_df):\n", + " # No target filtration on FDR, which is the same as DiaNN but different in Percolator\n", + " #train_t_df = train_t_df[train_t_df.fdr<=self.fdr]\n", + " train_df = pd.concat((train_t_df, train_d_df))\n", + " train_label = np.ones(len(train_df),dtype=np.int32)\n", + " train_label[len(train_t_df):] = 0\n", + "\n", + " self._ml_model.fit(\n", + " train_df[self.feature_list].values, \n", + " train_label\n", + " )\n", + " def rescore(self, psm_df):\n", + " # We don't need iteration anymore, but cross validation may be still necessary\n", + " df = self._cv_score(df)\n", + " return self._estimate_fdr(df)\n", + "```\n", + "\n", + "2. ML models. Personally, `Percolator` with a linear classifier (SVM or LogisticRegression) is prefered. But as a framework, we should support different ML models. We can easily switch to the random forest by `self.ml_model = RandomForestClassifier()`. We can also use a DL model which provides sklearn-like `fit()` and `decision_function()` APIs for rescoring." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "\n", + "class Percolator:\n", + " def __init__(self):\n", + " self._feature_extractor:BaseFeatureExtractor = BaseFeatureExtractor()\n", + " self._ml_model = LogisticRegression()\n", + " \n", + " self.fdr_level = 'psm' # psm, precursor, peptide, or sequence\n", + " self.fdr = 0.01\n", + " self.per_raw_fdr = False\n", + "\n", + " self.max_training_sample = 200000\n", + " self.min_training_sample = 100\n", + " self.cv_fold = 1\n", + " self.iter_num = 1\n", + "\n", + " @property\n", + " def feature_list(self)->list:\n", + " \"\"\" The read-only property to get extracted feature_list \"\"\"\n", + " return self.feature_extractor.feature_list\n", + "\n", + " @property\n", + " def ml_model(self):\n", + " return self._ml_model\n", + " \n", + " @ml_model.setter\n", + " def ml_model(self, model):\n", + " \"\"\" \n", + " `model` must be sklearn models or other models but implement \n", + " the same methods `fit()` and `decision_function()`/`predict_proba()` \n", + " as sklearn models\n", + " \"\"\"\n", + " self._ml_model = model\n", + "\n", + " @property\n", + " def feature_extractor(self)->BaseFeatureExtractor:\n", + " return self._feature_extractor\n", + " \n", + " @feature_extractor.setter\n", + " def feature_extractor(self, fe:BaseFeatureExtractor):\n", + " self._feature_extractor = fe\n", + "\n", + " def extract_features(self,\n", + " psm_df:pd.DataFrame,\n", + " *args, **kwargs\n", + " )->pd.DataFrame:\n", + " \"\"\"\n", + " Extract features for rescoring.\n", + "\n", + " *args and **kwargs are used for \n", + " `self.feature_extractor.extract_features`.\n", + "\n", + " Parameters\n", + " ----------\n", + " psm_df : pd.DataFrame\n", + " PSM DataFrame\n", + "\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " psm_df with feature columns appended inplace.\n", + " \"\"\"\n", + " psm_df['ml_score'] = psm_df.score\n", + " psm_df = self._estimate_psm_fdr(psm_df)\n", + " return self._feature_extractor.extract_features(\n", + " psm_df, *args, **kwargs\n", + " )\n", + "\n", + " def rescore(self, \n", + " df:pd.DataFrame\n", + " )->pd.DataFrame:\n", + " \"\"\"Rescore\n", + "\n", + " Parameters\n", + " ----------\n", + " df : pd.DataFrame\n", + " psm_df\n", + "\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " psm_df with `ml_score` and `fdr` columns updated inplace\n", + " \"\"\"\n", + " for i in range(self.iter_num):\n", + " df = self._cv_score(df)\n", + " df = self._estimate_fdr(df, 'psm', False)\n", + " df = self._estimate_fdr(df)\n", + " return df\n", + "\n", + " def run(self,\n", + " psm_df:pd.DataFrame,\n", + " *args, **kwargs\n", + " )->pd.DataFrame:\n", + " \"\"\"\n", + " Run percolator workflow:\n", + "\n", + " - self.extract_features()\n", + " - self.re_score()\n", + "\n", + " *args and **kwargs are used for \n", + " `self.feature_extractor.extract_features`.\n", + "\n", + " Parameters\n", + " ----------\n", + " psm_df : pd.DataFrame\n", + " PSM DataFrame\n", + "\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " psm_df with feature columns appended inplace.\n", + " \"\"\"\n", + " df = self.extract_features(\n", + " psm_df, *args, **kwargs\n", + " )\n", + " return self.rescore(df)\n", + "\n", + " def _estimate_fdr_per_raw(self,\n", + " df:pd.DataFrame,\n", + " fdr_level:str\n", + " )->pd.DataFrame:\n", + " df_list = []\n", + " for raw_name, df_raw in df.groupby('raw_name'):\n", + " df_list.append(self._estimate_fdr(df_raw, \n", + " fdr_level = fdr_level,\n", + " per_raw_fdr = False\n", + " ))\n", + " return pd.concat(df_list, ignore_index=True)\n", + "\n", + " def _estimate_psm_fdr(self,\n", + " df:pd.DataFrame,\n", + " )->pd.DataFrame:\n", + " df = df.sort_values(\n", + " ['ml_score','decoy'], ascending=False\n", + " ).reset_index(drop=True)\n", + " target_values = 1-df['decoy'].values\n", + " decoy_cumsum = np.cumsum(df['decoy'].values)\n", + " target_cumsum = np.cumsum(target_values)\n", + " fdr_values = decoy_cumsum/target_cumsum\n", + " df['fdr'] = fdr_to_q_values(fdr_values)\n", + " return df\n", + " \n", + " def _estimate_fdr(self, \n", + " df:pd.DataFrame,\n", + " fdr_level:str=None,\n", + " per_raw_fdr:bool=None,\n", + " )->pd.DataFrame:\n", + " if fdr_level is None: \n", + " fdr_level = self.fdr_level\n", + " if per_raw_fdr is None: \n", + " per_raw_fdr = self.per_raw_fdr\n", + "\n", + " if per_raw_fdr:\n", + " return self._estimate_fdr_per_raw(\n", + " df, fdr_level=fdr_level\n", + " )\n", + "\n", + " if fdr_level == 'psm':\n", + " return self._estimate_psm_fdr(df)\n", + " else:\n", + " if fdr_level == 'precursor':\n", + " _df = df.groupby([\n", + " 'sequence','mods','mod_sites','charge','decoy'\n", + " ])['ml_score'].max()\n", + " elif fdr_level == 'peptide':\n", + " _df = df.groupby([\n", + " 'sequence','mods','mod_sites','decoy'\n", + " ])['ml_score'].max()\n", + " else:\n", + " _df = df.groupby(['sequence','decoy'])['ml_score'].max()\n", + " _df = self._estimate_psm_fdr(_df)\n", + " df['fdr'] = fdr_from_ref(\n", + " df['ml_score'].values, _df['ml_score'].values, \n", + " _df['fdr'].values\n", + " )\n", + " return df\n", + "\n", + " def _train(self, \n", + " train_t_df:pd.DataFrame, \n", + " train_d_df:pd.DataFrame\n", + " ):\n", + " train_t_df = train_t_df[train_t_df.fdr<=self.fdr]\n", + "\n", + " if len(train_t_df) > self.max_train_sample:\n", + " train_t_df = train_t_df.sample(\n", + " n=self.max_training_sample, \n", + " random_state=1337\n", + " )\n", + " if len(train_d_df) > self.max_train_sample:\n", + " train_d_df = train_d_df.sample(\n", + " n=self.max_training_sample,\n", + " random_state=1337\n", + " )\n", + "\n", + " train_df = pd.concat((train_t_df, train_d_df))\n", + " train_label = np.ones(len(train_df),dtype=np.int32)\n", + " train_label[len(train_t_df):] = 0\n", + "\n", + " self._ml_model.fit(\n", + " train_df[self.feature_list].values, \n", + " train_label\n", + " )\n", + "\n", + " def _predict(self, test_df):\n", + " try:\n", + " test_df['ml_score'] = self._ml_model.decision_function(\n", + " test_df[self.feature_list].values\n", + " )\n", + " except AttributeError:\n", + " test_df['ml_score'] = self._ml_model.predict_proba(\n", + " test_df[self.feature_list].values\n", + " )\n", + " return test_df\n", + "\n", + " def _cv_score(self, df:pd.DataFrame)->pd.DataFrame:\n", + " \"\"\"\n", + " Apply cross-validation for rescoring.\n", + "\n", + " It will split `df` into K folds. For each fold, \n", + " its ML scores are predicted by a model which \n", + " is trained by other K-1 folds .\n", + "\n", + " Parameters\n", + " ----------\n", + " df : pd.DataFrame\n", + " PSMs to be rescored\n", + "\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " PSMs after rescoring\n", + " \"\"\"\n", + " df = df.sample(\n", + " frac=1, random_state=1337\n", + " ).reset_index(drop=True)\n", + " df_target = df[df.decoy == 0]\n", + " df_decoy = df[df.decoy != 0]\n", + "\n", + " if (\n", + " np.sum(df_target.fdr 1:\n", + " test_df_list = []\n", + " for i in range(self.cv_fold):\n", + " t_mask = np.ones(len(df_target), dtype=bool)\n", + " _slice = slice(i, len(df_target), self.cv_fold)\n", + " t_mask[_slice] = False\n", + " train_t_df = df_target[t_mask]\n", + " test_t_df = df_target[_slice]\n", + " \n", + " d_mask = np.ones(len(df_decoy), dtype=bool)\n", + " _slice = slice(i, len(df_decoy), self.cv_fold)\n", + " d_mask[_slice] = False\n", + " train_d_df = df_decoy[d_mask]\n", + " test_d_df = df_decoy[_slice]\n", + "\n", + " self._train(train_t_df, train_d_df)\n", + "\n", + " test_df = pd.concat((test_t_df, test_d_df))\n", + " test_df_list.append(self._predict(test_df))\n", + " \n", + " return pd.concat(test_df_list, ignore_index=True)\n", + " else:\n", + "\n", + " self._train(df_target, df_decoy)\n", + " test_df = pd.concat((df_target, df_decoy),ignore_index=True)\n", + " \n", + " return self._predict(test_df)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.3 ('base')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.3" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "8a3b27e141e49c996c9b863f8707e97aabd49c4a7e8445b9b783b34e4a21a9b2" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From d4fa4b32e9865bf6c2e4a29c9ffe88d4cd4bf341 Mon Sep 17 00:00:00 2001 From: "Zeng, Wen-Feng" Date: Mon, 10 Oct 2022 22:05:50 +0200 Subject: [PATCH 08/52] percolator --- alphabase/_modidx.py | 8 +- alphabase/scoring/feature_extraction_base.py | 6 +- alphabase/scoring/ml_scoring_base.py | 162 ++- docs/constants/aa.html | 42 +- docs/constants/element.html | 42 +- docs/constants/isotope.html | 42 +- docs/constants/modification.html | 42 +- docs/index.html | 42 +- docs/io/hdf.html | 42 +- docs/peptide/fragment.html | 42 +- docs/peptide/mass_calc.html | 42 +- docs/peptide/mobility.html | 42 +- docs/peptide/precursor.html | 42 +- docs/protein/fasta.html | 42 +- docs/protein/test_fasta.html | 42 +- docs/psm_reader/alphapept_reader.html | 42 +- docs/psm_reader/dia_psm_reader.html | 42 +- docs/psm_reader/maxquant_reader.html | 42 +- docs/psm_reader/msfragger_reader.html | 42 +- docs/psm_reader/pfind_reader.html | 42 +- docs/psm_reader/psm_reader.html | 42 +- docs/scoring/fdr.html | 971 ++++++++++++++ docs/scoring/feature_extraction_base.html | 520 ++++++++ docs/scoring/ml_scoring_base.html | 1151 +++++++++++++++++ docs/search.json | 49 + docs/sitemap.xml | 60 +- docs/spectral_library/decoy_library.html | 42 +- docs/spectral_library/library_base.html | 42 +- docs/statistics/regression.html | 655 ++++++++++ .../figure-html/cell-5-output-1.png | Bin 0 -> 28935 bytes docs/utils.html | 42 +- docs/yaml_utils.html | 42 +- nbdev_nbs/psm_reader/dia_psm_reader.ipynb | 9 - nbdev_nbs/scoring/fdr.ipynb | 30 +- .../scoring/feature_extraction_base.ipynb | 89 +- nbdev_nbs/scoring/ml_scoring_base.ipynb | 979 +++++++++++++- nbdev_nbs/sidebar.yml | 10 +- 37 files changed, 5437 insertions(+), 186 deletions(-) create mode 100644 docs/scoring/fdr.html create mode 100644 docs/scoring/feature_extraction_base.html create mode 100644 docs/scoring/ml_scoring_base.html create mode 100644 docs/statistics/regression.html create mode 100644 docs/statistics/regression_files/figure-html/cell-5-output-1.png diff --git a/alphabase/_modidx.py b/alphabase/_modidx.py index 27d6f5bc..8f4978ef 100644 --- a/alphabase/_modidx.py +++ b/alphabase/_modidx.py @@ -418,6 +418,8 @@ 'alphabase/scoring/ml_scoring_base.py'), 'alphabase.scoring.ml_scoring_base.Percolator._train': ( 'scoring/ml_scoring_base.html#percolator._train', 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator._train_and_score': ( 'scoring/ml_scoring_base.html#percolator._train_and_score', + 'alphabase/scoring/ml_scoring_base.py'), 'alphabase.scoring.ml_scoring_base.Percolator.extract_features': ( 'scoring/ml_scoring_base.html#percolator.extract_features', 'alphabase/scoring/ml_scoring_base.py'), 'alphabase.scoring.ml_scoring_base.Percolator.feature_extractor': ( 'scoring/ml_scoring_base.html#percolator.feature_extractor', @@ -428,8 +430,10 @@ 'alphabase/scoring/ml_scoring_base.py'), 'alphabase.scoring.ml_scoring_base.Percolator.rescore': ( 'scoring/ml_scoring_base.html#percolator.rescore', 'alphabase/scoring/ml_scoring_base.py'), - 'alphabase.scoring.ml_scoring_base.Percolator.run': ( 'scoring/ml_scoring_base.html#percolator.run', - 'alphabase/scoring/ml_scoring_base.py')}, + 'alphabase.scoring.ml_scoring_base.Percolator.run_rerank_workflow': ( 'scoring/ml_scoring_base.html#percolator.run_rerank_workflow', + 'alphabase/scoring/ml_scoring_base.py'), + 'alphabase.scoring.ml_scoring_base.Percolator.run_rescore_workflow': ( 'scoring/ml_scoring_base.html#percolator.run_rescore_workflow', + 'alphabase/scoring/ml_scoring_base.py')}, 'alphabase.spectral_library.decoy_library': { 'alphabase.spectral_library.decoy_library.DecoyLib': ( 'spectral_library/decoy_library.html#decoylib', 'alphabase/spectral_library/decoy_library.py'), 'alphabase.spectral_library.decoy_library.DecoyLib.__init__': ( 'spectral_library/decoy_library.html#decoylib.__init__', diff --git a/alphabase/scoring/feature_extraction_base.py b/alphabase/scoring/feature_extraction_base.py index 02778b8f..5d624c5f 100644 --- a/alphabase/scoring/feature_extraction_base.py +++ b/alphabase/scoring/feature_extraction_base.py @@ -35,15 +35,17 @@ def extract_features(self, Extract the scoring features (self._feature_list) and append them inplace into candidate PSMs (psm_df). + **All sub-classes must re-implement this method.** + Parameters ---------- psm_df : pd.DataFrame - PSMs to be rescore. + PSMs to be rescored Returns ------- pd.DataFrame - psm_df with appended the feature list extracted by this extractor. + psm_df with appended feature columns extracted by this extractor """ return psm_df diff --git a/alphabase/scoring/ml_scoring_base.py b/alphabase/scoring/ml_scoring_base.py index 7f146a56..5bcbe809 100644 --- a/alphabase/scoring/ml_scoring_base.py +++ b/alphabase/scoring/ml_scoring_base.py @@ -33,26 +33,35 @@ def __init__(self): self.cv_fold = 1 self.iter_num = 1 + self._base_features = ['score','nAA','charge'] + @property def feature_list(self)->list: - """ The read-only property to get extracted feature_list """ - return self.feature_extractor.feature_list + """ Get extracted feature_list. Property, read-only """ + return list(set( + self._base_features+ + self.feature_extractor.feature_list + )) @property def ml_model(self): + """ + ML model in Percolator. + It can be sklearn models or other models but implement + the methods `fit()` and `decision_function()` (or `predict_proba()`) + which are the same as sklearn models. + """ return self._ml_model @ml_model.setter def ml_model(self, model): - """ - `model` must be sklearn models or other models but implement - the same methods `fit()` and `decision_function()`/`predict_proba()` - as sklearn models - """ self._ml_model = model @property def feature_extractor(self)->BaseFeatureExtractor: + """ + The feature extractor inherited from `BaseFeatureExtractor` + """ return self._feature_extractor @feature_extractor.setter @@ -88,7 +97,8 @@ def extract_features(self, def rescore(self, df:pd.DataFrame )->pd.DataFrame: - """Rescore + """ + Estimate ML scores and then FDRs (q-values) Parameters ---------- @@ -106,7 +116,61 @@ def rescore(self, df = self._estimate_fdr(df) return df - def run(self, + def run_rerank_workflow(self, + top_k_psm_df:pd.DataFrame, + rerank_column:str='spec_idx', + *args, **kwargs + )->pd.DataFrame: + """ + Run percolator workflow with reranking + the peptides for each spectrum. + + - self.extract_features() + - self.rescore() + + *args and **kwargs are used for + `self.feature_extractor.extract_features`. + + Parameters + ---------- + top_k_psm_df : pd.DataFrame + PSM DataFrame + + rerank_column : str + The column use to rerank PSMs. + + For example, use the following code to select + the top-ranked peptide for each spectrum. + ``` + rerank_column = 'spec_idx' # scan_num + idx = top_k_psm_df.groupby( + ['raw_name',rerank_column] + )['ml_score'].idxmax() + psm_df = top_k_psm_df.loc[idx].copy() + ``` + Returns + ------- + pd.DataFrame + Only top-scored PSM is returned for + each group of the `rerank_column`. + """ + top_k_psm_df = self.extract_features( + top_k_psm_df, *args, **kwargs + ) + idxmax = top_k_psm_df.groupby( + ['raw_name',rerank_column] + )['ml_score'].idxmax() + + df = top_k_psm_df.loc[idxmax].copy() + self._train_and_score(df) + + top_k_psm_df = self._predict(top_k_psm_df) + idxmax = top_k_psm_df.groupby( + ['raw_name',rerank_column] + )['ml_score'].idxmax() + return top_k_psm_df.loc[idxmax].copy() + + def run_rescore_workflow(self, psm_df:pd.DataFrame, *args, **kwargs )->pd.DataFrame: @@ -114,7 +178,7 @@ def run(self, Run percolator workflow: - self.extract_features() - - self.re_score() + - self.rescore() *args and **kwargs are used for `self.feature_extractor.extract_features`. @@ -200,12 +264,12 @@ def _train(self, ): train_t_df = train_t_df[train_t_df.fdr<=self.fdr] - if len(train_t_df) > self.max_train_sample: + if len(train_t_df) > self.max_training_sample: train_t_df = train_t_df.sample( n=self.max_training_sample, random_state=1337 ) - if len(train_d_df) > self.max_train_sample: + if len(train_d_df) > self.max_training_sample: train_d_df = train_d_df.sample( n=self.max_training_sample, random_state=1337 @@ -231,6 +295,28 @@ def _predict(self, test_df): ) return test_df + def _train_and_score(self, + df:pd.DataFrame + )->pd.DataFrame: + + df_target = df[df.decoy == 0] + df_decoy = df[df.decoy != 0] + + if ( + np.sum(df_target.fdr<=self.fdr) < + self.min_training_sample or + len(df_decoy) < self.min_training_sample + ): + return df + + self._train(df_target, df_decoy) + test_df = pd.concat( + [df_target, df_decoy], + ignore_index=True + ) + + return self._predict(test_df) + def _cv_score(self, df:pd.DataFrame)->pd.DataFrame: """ Apply cross-validation for rescoring. @@ -249,9 +335,14 @@ def _cv_score(self, df:pd.DataFrame)->pd.DataFrame: pd.DataFrame PSMs after rescoring """ + + if self.cv_fold <= 1: + return self._train_and_score(df) + df = df.sample( frac=1, random_state=1337 ).reset_index(drop=True) + df_target = df[df.decoy == 0] df_decoy = df[df.decoy != 0] @@ -263,31 +354,24 @@ def _cv_score(self, df:pd.DataFrame)->pd.DataFrame: ): return df - if self.cv_fold > 1: - test_df_list = [] - for i in range(self.cv_fold): - t_mask = np.ones(len(df_target), dtype=bool) - _slice = slice(i, len(df_target), self.cv_fold) - t_mask[_slice] = False - train_t_df = df_target[t_mask] - test_t_df = df_target[_slice] - - d_mask = np.ones(len(df_decoy), dtype=bool) - _slice = slice(i, len(df_decoy), self.cv_fold) - d_mask[_slice] = False - train_d_df = df_decoy[d_mask] - test_d_df = df_decoy[_slice] - - self._train(train_t_df, train_d_df) - - test_df = pd.concat((test_t_df, test_d_df)) - test_df_list.append(self._predict(test_df)) - - return pd.concat(test_df_list, ignore_index=True) - else: - - self._train(df_target, df_decoy) - test_df = pd.concat((df_target, df_decoy),ignore_index=True) - - return self._predict(test_df) + test_df_list = [] + for i in range(self.cv_fold): + t_mask = np.ones(len(df_target), dtype=bool) + _slice = slice(i, len(df_target), self.cv_fold) + t_mask[_slice] = False + train_t_df = df_target[t_mask] + test_t_df = df_target[_slice] + + d_mask = np.ones(len(df_decoy), dtype=bool) + _slice = slice(i, len(df_decoy), self.cv_fold) + d_mask[_slice] = False + train_d_df = df_decoy[d_mask] + test_d_df = df_decoy[_slice] + + self._train(train_t_df, train_d_df) + + test_df = pd.concat((test_t_df, test_d_df)) + test_df_list.append(self._predict(test_df)) + + return pd.concat(test_df_list, ignore_index=True) diff --git a/docs/constants/aa.html b/docs/constants/aa.html index 75ea6bca..74e8c097 100644 --- a/docs/constants/aa.html +++ b/docs/constants/aa.html @@ -328,7 +328,7 @@

Amino acid information

+ + diff --git a/docs/constants/element.html b/docs/constants/element.html index 8a54c8c3..bdae7455 100644 --- a/docs/constants/element.html +++ b/docs/constants/element.html @@ -262,7 +262,7 @@

Atom element information

+ + diff --git a/docs/constants/isotope.html b/docs/constants/isotope.html index fb0e47f9..1defb1d6 100644 --- a/docs/constants/isotope.html +++ b/docs/constants/isotope.html @@ -325,7 +325,7 @@

Isotope distribution

+ + diff --git a/docs/constants/modification.html b/docs/constants/modification.html index 86463909..d11df70c 100644 --- a/docs/constants/modification.html +++ b/docs/constants/modification.html @@ -328,7 +328,7 @@

Modification information

+ + diff --git a/docs/index.html b/docs/index.html index b3e6f02e..67bb8e94 100644 --- a/docs/index.html +++ b/docs/index.html @@ -325,7 +325,7 @@

AlphaBase

+ + diff --git a/docs/io/hdf.html b/docs/io/hdf.html index e23020e9..64ac49d0 100644 --- a/docs/io/hdf.html +++ b/docs/io/hdf.html @@ -325,7 +325,7 @@

HDF functionalities

+ + diff --git a/docs/peptide/fragment.html b/docs/peptide/fragment.html index ca773653..81b43b52 100644 --- a/docs/peptide/fragment.html +++ b/docs/peptide/fragment.html @@ -328,7 +328,7 @@

Fragment Functionalities

+ + diff --git a/docs/peptide/mass_calc.html b/docs/peptide/mass_calc.html index a581be70..159a4624 100644 --- a/docs/peptide/mass_calc.html +++ b/docs/peptide/mass_calc.html @@ -325,7 +325,7 @@

Mass Calculation

+ + diff --git a/docs/peptide/mobility.html b/docs/peptide/mobility.html index f20f1cc7..db3e1094 100644 --- a/docs/peptide/mobility.html +++ b/docs/peptide/mobility.html @@ -262,7 +262,7 @@

CCS/Mobility Functionalities

+ + diff --git a/docs/peptide/precursor.html b/docs/peptide/precursor.html index 10aa23d7..08a3d147 100644 --- a/docs/peptide/precursor.html +++ b/docs/peptide/precursor.html @@ -325,7 +325,7 @@

Precursor Functionalities

+ + diff --git a/docs/protein/fasta.html b/docs/protein/fasta.html index 4892f583..8682adc6 100644 --- a/docs/protein/fasta.html +++ b/docs/protein/fasta.html @@ -328,7 +328,7 @@

Protein and Peptide Processing

+ + diff --git a/docs/protein/test_fasta.html b/docs/protein/test_fasta.html index 70b86f90..305b2626 100644 --- a/docs/protein/test_fasta.html +++ b/docs/protein/test_fasta.html @@ -328,7 +328,7 @@

Testing fasta

+ + diff --git a/docs/psm_reader/alphapept_reader.html b/docs/psm_reader/alphapept_reader.html index 92db61b2..b3ef4fa6 100644 --- a/docs/psm_reader/alphapept_reader.html +++ b/docs/psm_reader/alphapept_reader.html @@ -325,7 +325,7 @@

AlphaPept PSM Reader

+ + diff --git a/docs/psm_reader/dia_psm_reader.html b/docs/psm_reader/dia_psm_reader.html index 58729b76..ec78d4c5 100644 --- a/docs/psm_reader/dia_psm_reader.html +++ b/docs/psm_reader/dia_psm_reader.html @@ -328,7 +328,7 @@

DIA PSM reader

+ + diff --git a/docs/psm_reader/maxquant_reader.html b/docs/psm_reader/maxquant_reader.html index b68a0465..ef50232a 100644 --- a/docs/psm_reader/maxquant_reader.html +++ b/docs/psm_reader/maxquant_reader.html @@ -328,7 +328,7 @@

MaxQuant PSM reader

+ + diff --git a/docs/psm_reader/msfragger_reader.html b/docs/psm_reader/msfragger_reader.html index b7e94443..9b8765ee 100644 --- a/docs/psm_reader/msfragger_reader.html +++ b/docs/psm_reader/msfragger_reader.html @@ -262,7 +262,7 @@

MSFragger Reader

+ + diff --git a/docs/psm_reader/pfind_reader.html b/docs/psm_reader/pfind_reader.html index 011d3a92..86ee4938 100644 --- a/docs/psm_reader/pfind_reader.html +++ b/docs/psm_reader/pfind_reader.html @@ -325,7 +325,7 @@

pFind PSM Reader

+ + diff --git a/docs/psm_reader/psm_reader.html b/docs/psm_reader/psm_reader.html index f6075814..7c658c64 100644 --- a/docs/psm_reader/psm_reader.html +++ b/docs/psm_reader/psm_reader.html @@ -325,7 +325,7 @@

Base Class for PSM Readers

+ + diff --git a/docs/scoring/fdr.html b/docs/scoring/fdr.html new file mode 100644 index 00000000..769d16c4 --- /dev/null +++ b/docs/scoring/fdr.html @@ -0,0 +1,971 @@ + + + + + + + + + +alphabase - FDR functionalities + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + + + + +
+ +
+
+

FDR functionalities

+
+ + + +
+ + + +
+ + +
+ + +

Functionalities to calculate FDR.

+
+

In alphabase dataframes, we refer fdr values as q_values without loss of generacity.

+
+
+

source

+
+

calculate_fdr_from_ref

+
+
 calculate_fdr_from_ref (df:pandas.core.frame.DataFrame,
+                         ref_scores:numpy.ndarray,
+                         ref_fdr_values:numpy.ndarray, score_column:str,
+                         decoy_column:str='decoy')
+
+

Calculate FDR values for a PSM dataframe from the given reference scores and fdr_values. It is used to extend peptide-level or sequence-level FDR (reference) to each PSM, as PSMs are more useful for quantification. ``

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
dfDataFramePSM dataframe
ref_scoresndarrayreference scores that used to
calculate ref_fdr_values, also sorted in decending order.
ref_fdr_valuesndarrayfdr values corresponding to ref_scores
score_columnstrscore column in the dataframe
decoy_columnstrdecoydecoy column in the dataframe.
1=target, 0=decoy. Defaults to ‘decoy’.
ReturnsDataFramedataframe with ‘fdr’ column added
+
+

source

+
+
+

fdr_from_ref

+
+
 fdr_from_ref (sorted_scores:numpy.ndarray, ref_scores:numpy.ndarray,
+               ref_fdr_values:numpy.ndarray)
+
+

Calculate FDR values from the given reference scores and fdr_values. It is used to extend peptide-level or sequence-level FDR (reference) to each PSM, as PSMs are more useful for quantification.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
sorted_scoresndarraythe scores to calculate FDRs,
they must be sorted in decending order.
ref_scoresndarrayreference scores that used to
calculate ref_fdr_values, also sorted in decending order.
ref_fdr_valuesndarrayfdr values corresponding to ref_scores
Returnsndarrayfdr values corresponding to sorted_scores.
+
+

source

+
+
+

calculate_fdr

+
+
 calculate_fdr (df:pandas.core.frame.DataFrame, score_column:str,
+                decoy_column:str='decoy')
+
+

Calculate FDR values (q_values in fact) for the given dataframe

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
dfDataFramePSM dataframe to calculate FDRs
score_columnstrscore column to sort in decending order
decoy_columnstrdecoydecoy column in the dataframe.
1=target, 0=decoy. Defaults to ‘decoy’.
ReturnsDataFramePSM dataframe with ‘fdr’ column added
+
+

source

+
+
+

fdr_to_q_values

+
+
 fdr_to_q_values (fdr_values:numpy.ndarray)
+
+

convert FDR values to q_values.

+ +++++ + + + + + + + + + + + + + + + + + + + +
TypeDetails
fdr_valuesndarrayFDR values, they should be
sorted according to the descending order of the score
Returnsndarrayq_values
+
+
df = pd.DataFrame(
+    {
+        'score': np.random.random(500)*10+11,
+        'decoy': 0,
+        'kind': True,
+    }
+)
+f_score = np.random.random(500)*9.9
+df = df.append(
+    pd.DataFrame(
+        {
+            'score': f_score+0.01,
+            'decoy': 1,
+            'kind': False
+        }
+    )
+)
+df = df.append(
+    pd.DataFrame(
+        {
+            'score': f_score,
+            'decoy': 0,
+            'kind': False
+        }
+    )
+)
+df = df.append(
+    pd.DataFrame(
+        {
+            'score': np.random.random(5)+10,
+            'decoy': 1,
+            'kind': False
+        }
+    )
+)
+
+df = calculate_fdr(df, 'score', 'decoy')
+df
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
scoredecoykindfdr
41720.9867510True0.000000
48620.9588030True0.000000
4620.9542440True0.000000
13120.8324400True0.000000
23620.8095950True0.000000
...............
11110.0463660False0.504008
7090.0408411False0.504505
12090.0308410False0.504505
9390.0137041False0.505000
14390.0037040False0.505000
+

1505 rows × 4 columns

+
+
+
+
+
df[(df.fdr < 0.01)&(df.decoy==0)]
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
scoredecoykindfdr
41720.9867510True0.0
48620.9588030True0.0
4620.9542440True0.0
13120.8324400True0.0
23620.8095950True0.0
...............
31311.0706950True0.0
22711.0284310True0.0
15311.0143300True0.0
11311.0139780True0.0
4811.0106290True0.0
+

500 rows × 4 columns

+
+
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/docs/scoring/feature_extraction_base.html b/docs/scoring/feature_extraction_base.html new file mode 100644 index 00000000..e23a8359 --- /dev/null +++ b/docs/scoring/feature_extraction_base.html @@ -0,0 +1,520 @@ + + + + + + + + + +alphabase - Base Class of Feature Extractors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + + + + +
+ +
+
+

Base Class of Feature Extractors

+
+ + + +
+ + + +
+ + +
+ + +
+

source

+
+

BaseFeatureExtractor

+
+
 BaseFeatureExtractor ()
+
+

Initialize self. See help(type(self)) for accurate signature.

+
+

source

+
+
+

BaseFeatureExtractor.extract_features

+
+
 BaseFeatureExtractor.extract_features
+                                        (psm_df:pandas.core.frame.DataFram
+                                        e, *args, **kwargs)
+
+

Extract the scoring features (self._feature_list) and append them inplace into candidate PSMs (psm_df).

+

All sub-classes must re-implement this method.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
psm_dfDataFramePSMs to be rescored
args
kwargs
ReturnsDataFramepsm_df with appended feature columns extracted by this extractor
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/docs/scoring/ml_scoring_base.html b/docs/scoring/ml_scoring_base.html new file mode 100644 index 00000000..2a47c7bf --- /dev/null +++ b/docs/scoring/ml_scoring_base.html @@ -0,0 +1,1151 @@ + + + + + + + + + +alphabase - Base Class of ML Scoring Methods + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + + + + +
+ +
+
+

Base Class of ML Scoring Methods

+
+ + + +
+ + + +
+ + +
+ + +

There are two key modules in ML-based rescoring: feature extraction and rescoring algorithm. Here we designed these two modules as flexible as possible for future extensions.

+
+

Feature extraction

+

The feature extractor is more important than the ML methods, so we designed a flexible architecture for feature extraction. As shown in BaseFeatureExtractor, a feature extractor inherited from BaseFeatureExtractor must re-implement BaseFeatureExtractor.extract_features, and tells the ML methods what are the extracted features by providing BaseFeatureExtractor.feature_list.

+

For example, if we have two feature extractors, AlphaPeptFE and AlphaPeptDeepFE:

+
class AlphaPeptFE(BaseFeatureExtractor):
+    def extract_features(self, psm_df):
+        psm_df['ap_f1'] = ...
+        self._feature_list.append('ap_f1')
+        psm_df['ap_f2'] = ...
+        self._feature_list.append('ap_f2')
+
+class AlphaPeptDeepFE(BaseFeatureExtractor):
+    def extract_features(self, psm_df):
+        psm_df['ad_f1'] = ...
+        self._feature_list.append('ad_f1')
+        psm_df['ad_f2'] = ...
+        self._feature_list.append('ad_f2')
+

We can easily design a new feature extractor which combines these two and more feature extractors:

+
class CombFE(BaseFeatureExtractor):
+    def __init__(self):
+        self.fe_list = [AlphaPeptFE(),AlphaPeptDeepFE()]
+
+    def extract_features(self, psm_df):
+        for fe in self.fe_list:
+            fe.extract_features(psm_df)
+
+    @property
+    def feature_list(self):
+        f_set = set()
+        for fe in self.fe_list:
+            f_set.update(fe.feature_list)
+        return list(f_set)
+

This will be useful for rescoring with DL features, for instance, when AlphaPeptDeep is or is not installed.

+
+
+

Rescoring Algorithm

+

The rescoring algorithm called Percolator (Kall et al. 2007) based on the semi-supervised learning algorithm is still the most widely used in MS-based proteomics. Therefore, we used Percolator as the base rescoring class and others can re-implement its methods for different algorithms. as well as different

+
    +
  1. Rescoring algorithm. We have provided the base rescoring code structure in Percolator. If we are going to support DiaNN’s brute-force supervised learning methods, we can define the class like this:
  2. +
+
class DiaNNRescoring(Percolator):
+    def _train(self, train_t_df, train_d_df):
+        # No target filtration on FDR, which is the same as DiaNN but different from Percolator
+        #train_t_df = train_t_df[train_t_df.fdr<=self.fdr]
+        train_df = pd.concat((train_t_df, train_d_df))
+        train_label = np.ones(len(train_df),dtype=np.int32)
+        train_label[len(train_t_df):] = 0
+
+        self._ml_model.fit(
+            train_df[self.feature_list].values, 
+            train_label
+        )
+    def rescore(self, psm_df):
+        # We don't need iteration anymore, but cross validation is still necessary
+        df = self._cv_score(df)
+        return self._estimate_fdr(df)
+
    +
  1. ML models. Personally, Percolator with a linear classifier (SVM or LogisticRegression) is prefered. But as a framework, we should support different ML models. We can easily switch to the random forest by self.ml_model = RandomForestClassifier(). We can also use a DL model which provides sklearn-like fit() and decision_function() APIs for rescoring.
  2. +
+
+

source

+
+

Percolator

+
+
 Percolator ()
+
+

Initialize self. See help(type(self)) for accurate signature.

+
+
+

Properties of Percolator

+
+

source

+
+
+

Percolator.ml_model

+
+
 Percolator.ml_model ()
+
+

ML model in Percolator. It can be sklearn models or other models but implement the methods fit() and decision_function() (or predict_proba()) which are the same as sklearn models.

+
+

source

+
+
+

Percolator.feature_extractor

+
+
 Percolator.feature_extractor ()
+
+

The feature extractor inherited from BaseFeatureExtractor

+
+

source

+
+
+

Percolator.feature_list

+
+
 Percolator.feature_list ()
+
+

Get extracted feature_list. Property, read-only

+
+
+

Methods of Percolator

+
+

source

+
+
+

Percolator.extract_features

+
+
 Percolator.extract_features (psm_df:pandas.core.frame.DataFrame, *args,
+                              **kwargs)
+
+

Extract features for rescoring.

+

*args and **kwargs are used for self.feature_extractor.extract_features.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
psm_dfDataFramePSM DataFrame
args
kwargs
ReturnsDataFramepsm_df with feature columns appended inplace.
+
+

source

+
+
+

Percolator.rescore

+
+
 Percolator.rescore (df:pandas.core.frame.DataFrame)
+
+

Estimate ML scores and then FDRs (q-values)

+ +++++ + + + + + + + + + + + + + + + + + + + +
TypeDetails
dfDataFramepsm_df
ReturnsDataFramepsm_df with ml_score and fdr columns updated inplace
+
+

source

+
+
+

Percolator.run_rescore_workflow

+
+
 Percolator.run_rescore_workflow (psm_df:pandas.core.frame.DataFrame,
+                                  *args, **kwargs)
+
+

Run percolator workflow:

+
    +
  • self.extract_features()
  • +
  • self.rescore()
  • +
+

*args and **kwargs are used for self.feature_extractor.extract_features.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
psm_dfDataFramePSM DataFrame
args
kwargs
ReturnsDataFramepsm_df with feature columns appended inplace.
+
+

source

+
+
+

Percolator.run_rerank_workflow

+
+
 Percolator.run_rerank_workflow (top_k_psm_df:pandas.core.frame.DataFrame,
+                                 rerank_column:str='spec_idx', *args,
+                                 **kwargs)
+
+

Run percolator workflow with reranking the peptides for each spectrum.

+
    +
  • self.extract_features()
  • +
  • self.rescore()
  • +
+

*args and **kwargs are used for self.feature_extractor.extract_features.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
top_k_psm_dfDataFramePSM DataFrame
rerank_columnstrspec_idxThe column use to rerank PSMs.

For example, use the following code to select
the top-ranked peptide for each spectrum.
<br>rerank_column = 'spec_idx' # scan_num<br>idx = top_k_psm_df.groupby(<br> ['raw_name',rerank_column]<br>)['ml_score'].idxmax()<br>psm_df = top_k_psm_df.loc[idx].copy()<br>
args
kwargs
ReturnsDataFrame
+
+
+
+

Simple Examples

+
+
df = pd.DataFrame({
+    'score': list(np.random.uniform(0,100,100))+list(np.random.uniform(0,10,100)),
+    'nAA': list(np.random.randint(7,30,200)),
+    'charge': list(np.random.randint(2,4,200)),
+    'decoy': [0]*100+[1]*100,
+    'spec_idx': np.repeat(np.arange(100),2),
+    'raw_name': 'raw',
+})
+perc = Percolator()
+perc.min_training_sample = 10
+perc.run_rescore_workflow(df)
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
scorenAAchargedecoyspec_idxraw_nameml_scorefdr
099.851979263018raw138.1427660.000000
198.74605273012raw133.8677790.000000
297.415167162016raw133.4477610.000000
396.857314143015raw131.8773180.000000
494.606208173048raw128.7857130.000000
...........................
1950.346523182189raw-17.0086490.979798
1960.703782153182raw-17.2927480.989899
1970.058571223177raw-17.3522931.000000
1980.90198392164raw-17.3577041.000000
1990.32037882031raw-18.3954211.000000
+

200 rows × 8 columns

+
+
+
+
+
perc.run_rerank_workflow(df, rerank_column='spec_idx')
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
scorenAAchargedecoyspec_idxraw_nameml_scorefdr
5444.98600025200raw23.2398710.000000
694.0206587301raw61.7629730.000000
7323.02806814202raw5.3460260.000000
1779.16353728303raw50.5846930.000000
3661.67392323204raw36.5007280.000000
...........................
1702.30608673195raw-11.4759200.744898
1058.10719282196raw-6.7650490.191011
929.717331103197raw-5.4596660.044944
1434.381494293198raw-9.1000270.565217
1305.831152263199raw-8.0403860.423913
+

100 rows × 8 columns

+
+
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/docs/search.json b/docs/search.json index 26705de7..9f935bfb 100644 --- a/docs/search.json +++ b/docs/search.json @@ -6,6 +6,13 @@ "section": "", "text": "source\n\nexplode_multiple_columns\n\n explode_multiple_columns (df:pandas.core.frame.DataFrame, columns:list)\n\n\nsource\n\n\nprocess_bar\n\n process_bar (iterator, len_iter)" }, + { + "objectID": "statistics/regression.html", + "href": "statistics/regression.html", + "title": "alphabase", + "section": "", + "text": "LOESSRegression\n\n LOESSRegression (n_kernels:int=6, kernel_size:float=2.0,\n polynomial_degree:int=2)\n\nscikit-learn estimator which implements a LOESS style local polynomial regression. The number of basis functions or kernels can be explicitly defined which allows for faster and cheaper training and inference.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nn_kernels\nint\n6\ndefault = 6, The number of local polynomial functions used to approximate the data. The location and extend of the kernels will be distributed to contain an equal number of datapoints in the training set.\n\n\nkernel_size\nfloat\n2.0\ndefault = 2, A factor increasing the kernel size to overlap with the neighboring kernel.\n\n\npolynomial_degree\nint\n2\ndefault = 2, Degree of the polynomial functions used for the local approximation.\n\n\n\n\nsource\n\n\nLOESSRegression.fit\n\n LOESSRegression.fit (x:numpy.ndarray, y:numpy.ndarray)\n\nfit the model passed on provided training data.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nx\nndarray\nfloat, of shape (n_samples,) or (n_samples, 1), Training data. Note that only a single feature is supported at the moment.\n\n\ny\nndarray\nof shape (n_samples,) or (n_samples, 1) Target values.\n\n\nReturns\nself: object\nReturns the fitted estimator.\n\n\n\n\nsource\n\n\nLOESSRegression.predict\n\n LOESSRegression.predict (x:numpy.ndarray)\n\nPredict using the LOESS model.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nx\nndarray\nfloat, of shape (n_samples,) or (n_samples, 1) Feature data. Note that only a single feature is supported at the moment.\n\n\nReturns\nnumpy.ndarray, float\n\n\n\n\n\n\nApplication example\n\ndef noisy_1d(x):\n y = np.sin(x)\n y_err = np.random.normal(y,0.5)\n return y + y_err + 0.5 * x\n\nx_train = np.linspace(0,15,200)\ny_train = noisy_1d(x_train)\n\nx_test = np.linspace(0,15,200)\ny_test = LOESSRegression().fit(x_train, y_train).predict(x_test)\n\nplt.scatter(x_train,y_train)\nplt.plot(x_test,y_test,c='r')\nplt.show()" + }, { "objectID": "psm_reader/msfragger_reader.html", "href": "psm_reader/msfragger_reader.html", @@ -160,6 +167,48 @@ "section": "", "text": "import alphabase.io.hdf\n\n# Other packages used to demonstrate functionality\nimport numpy as np\nimport pandas as pd\nimport os\n\nInstead of relying directly on the h5py interface, we will use an HDF wrapper file to provide consistent access to only those specific HDF features we want. Since components of an HDF file come in three shapes datasets, groups and attributes, we will first define a generic HDF wrapper object to handle these components. Once this is done, the HDF wrapper file can be treated as such an object with additional features to open and close the initial connection.\n\n#| hide\nfrom nbdev.showdoc import show_doc\n\n\n\nHDF_File\n\n HDF_File (file_name:str, read_only:bool=True, truncate:bool=False,\n delete_existing:bool=False)\n\nA generic class to access HDF components." }, + { + "objectID": "scoring/feature_extraction_base.html", + "href": "scoring/feature_extraction_base.html", + "title": "Base Class of Feature Extractors", + "section": "", + "text": "source\n\nBaseFeatureExtractor\n\n BaseFeatureExtractor ()\n\nInitialize self. See help(type(self)) for accurate signature.\n\nsource\n\n\nBaseFeatureExtractor.extract_features\n\n BaseFeatureExtractor.extract_features\n (psm_df:pandas.core.frame.DataFram\n e, *args, **kwargs)\n\nExtract the scoring features (self._feature_list) and append them inplace into candidate PSMs (psm_df).\nAll sub-classes must re-implement this method.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\npsm_df\nDataFrame\nPSMs to be rescored\n\n\nargs\n\n\n\n\nkwargs\n\n\n\n\nReturns\nDataFrame\npsm_df with appended feature columns extracted by this extractor" + }, + { + "objectID": "scoring/ml_scoring_base.html", + "href": "scoring/ml_scoring_base.html", + "title": "Base Class of ML Scoring Methods", + "section": "", + "text": "There are two key modules in ML-based rescoring: feature extraction and rescoring algorithm. Here we designed these two modules as flexible as possible for future extensions." + }, + { + "objectID": "scoring/ml_scoring_base.html#feature-extraction", + "href": "scoring/ml_scoring_base.html#feature-extraction", + "title": "Base Class of ML Scoring Methods", + "section": "Feature extraction", + "text": "Feature extraction\nThe feature extractor is more important than the ML methods, so we designed a flexible architecture for feature extraction. As shown in BaseFeatureExtractor, a feature extractor inherited from BaseFeatureExtractor must re-implement BaseFeatureExtractor.extract_features, and tells the ML methods what are the extracted features by providing BaseFeatureExtractor.feature_list.\nFor example, if we have two feature extractors, AlphaPeptFE and AlphaPeptDeepFE:\nclass AlphaPeptFE(BaseFeatureExtractor):\n def extract_features(self, psm_df):\n psm_df['ap_f1'] = ...\n self._feature_list.append('ap_f1')\n psm_df['ap_f2'] = ...\n self._feature_list.append('ap_f2')\n\nclass AlphaPeptDeepFE(BaseFeatureExtractor):\n def extract_features(self, psm_df):\n psm_df['ad_f1'] = ...\n self._feature_list.append('ad_f1')\n psm_df['ad_f2'] = ...\n self._feature_list.append('ad_f2')\nWe can easily design a new feature extractor which combines these two and more feature extractors:\nclass CombFE(BaseFeatureExtractor):\n def __init__(self):\n self.fe_list = [AlphaPeptFE(),AlphaPeptDeepFE()]\n\n def extract_features(self, psm_df):\n for fe in self.fe_list:\n fe.extract_features(psm_df)\n\n @property\n def feature_list(self):\n f_set = set()\n for fe in self.fe_list:\n f_set.update(fe.feature_list)\n return list(f_set)\nThis will be useful for rescoring with DL features, for instance, when AlphaPeptDeep is or is not installed." + }, + { + "objectID": "scoring/ml_scoring_base.html#rescoring-algorithm", + "href": "scoring/ml_scoring_base.html#rescoring-algorithm", + "title": "Base Class of ML Scoring Methods", + "section": "Rescoring Algorithm", + "text": "Rescoring Algorithm\nThe rescoring algorithm called Percolator (Kall et al. 2007) based on the semi-supervised learning algorithm is still the most widely used in MS-based proteomics. Therefore, we used Percolator as the base rescoring class and others can re-implement its methods for different algorithms. as well as different\n\nRescoring algorithm. We have provided the base rescoring code structure in Percolator. If we are going to support DiaNN’s brute-force supervised learning methods, we can define the class like this:\n\nclass DiaNNRescoring(Percolator):\n def _train(self, train_t_df, train_d_df):\n # No target filtration on FDR, which is the same as DiaNN but different from Percolator\n #train_t_df = train_t_df[train_t_df.fdr<=self.fdr]\n train_df = pd.concat((train_t_df, train_d_df))\n train_label = np.ones(len(train_df),dtype=np.int32)\n train_label[len(train_t_df):] = 0\n\n self._ml_model.fit(\n train_df[self.feature_list].values, \n train_label\n )\n def rescore(self, psm_df):\n # We don't need iteration anymore, but cross validation is still necessary\n df = self._cv_score(df)\n return self._estimate_fdr(df)\n\nML models. Personally, Percolator with a linear classifier (SVM or LogisticRegression) is prefered. But as a framework, we should support different ML models. We can easily switch to the random forest by self.ml_model = RandomForestClassifier(). We can also use a DL model which provides sklearn-like fit() and decision_function() APIs for rescoring.\n\n\nsource\n\nPercolator\n\n Percolator ()\n\nInitialize self. See help(type(self)) for accurate signature.\n\n\nProperties of Percolator\n\nsource\n\n\nPercolator.ml_model\n\n Percolator.ml_model ()\n\nML model in Percolator. It can be sklearn models or other models but implement the methods fit() and decision_function() (or predict_proba()) which are the same as sklearn models.\n\nsource\n\n\nPercolator.feature_extractor\n\n Percolator.feature_extractor ()\n\nThe feature extractor inherited from BaseFeatureExtractor\n\nsource\n\n\nPercolator.feature_list\n\n Percolator.feature_list ()\n\nGet extracted feature_list. Property, read-only\n\n\nMethods of Percolator\n\nsource\n\n\nPercolator.extract_features\n\n Percolator.extract_features (psm_df:pandas.core.frame.DataFrame, *args,\n **kwargs)\n\nExtract features for rescoring.\n*args and **kwargs are used for self.feature_extractor.extract_features.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\npsm_df\nDataFrame\nPSM DataFrame\n\n\nargs\n\n\n\n\nkwargs\n\n\n\n\nReturns\nDataFrame\npsm_df with feature columns appended inplace.\n\n\n\n\nsource\n\n\nPercolator.rescore\n\n Percolator.rescore (df:pandas.core.frame.DataFrame)\n\nEstimate ML scores and then FDRs (q-values)\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\ndf\nDataFrame\npsm_df\n\n\nReturns\nDataFrame\npsm_df with ml_score and fdr columns updated inplace\n\n\n\n\nsource\n\n\nPercolator.run_rescore_workflow\n\n Percolator.run_rescore_workflow (psm_df:pandas.core.frame.DataFrame,\n *args, **kwargs)\n\nRun percolator workflow:\n\nself.extract_features()\nself.rescore()\n\n*args and **kwargs are used for self.feature_extractor.extract_features.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\npsm_df\nDataFrame\nPSM DataFrame\n\n\nargs\n\n\n\n\nkwargs\n\n\n\n\nReturns\nDataFrame\npsm_df with feature columns appended inplace.\n\n\n\n\nsource\n\n\nPercolator.run_rerank_workflow\n\n Percolator.run_rerank_workflow (top_k_psm_df:pandas.core.frame.DataFrame,\n rerank_column:str='spec_idx', *args,\n **kwargs)\n\nRun percolator workflow with reranking the peptides for each spectrum.\n\nself.extract_features()\nself.rescore()\n\n*args and **kwargs are used for self.feature_extractor.extract_features.\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ntop_k_psm_df\nDataFrame\n\nPSM DataFrame\n\n\nrerank_column\nstr\nspec_idx\nThe column use to rerank PSMs. For example, use the following code to select the top-ranked peptide for each spectrum.
rerank_column = 'spec_idx' # scan_num
idx = top_k_psm_df.groupby(
['raw_name',rerank_column]
)['ml_score'].idxmax()
psm_df = top_k_psm_df.loc[idx].copy()
\n\n\nargs\n\n\n\n\n\nkwargs\n\n\n\n\n\nReturns\nDataFrame" + }, + { + "objectID": "scoring/ml_scoring_base.html#simple-examples", + "href": "scoring/ml_scoring_base.html#simple-examples", + "title": "Base Class of ML Scoring Methods", + "section": "Simple Examples", + "text": "Simple Examples\n\ndf = pd.DataFrame({\n 'score': list(np.random.uniform(0,100,100))+list(np.random.uniform(0,10,100)),\n 'nAA': list(np.random.randint(7,30,200)),\n 'charge': list(np.random.randint(2,4,200)),\n 'decoy': [0]*100+[1]*100,\n 'spec_idx': np.repeat(np.arange(100),2),\n 'raw_name': 'raw',\n})\nperc = Percolator()\nperc.min_training_sample = 10\nperc.run_rescore_workflow(df)\n\n\n\n\n\n \n \n \n score\n nAA\n charge\n decoy\n spec_idx\n raw_name\n ml_score\n fdr\n \n \n \n \n 0\n 99.851979\n 26\n 3\n 0\n 18\n raw\n 138.142766\n 0.000000\n \n \n 1\n 98.746052\n 7\n 3\n 0\n 12\n raw\n 133.867779\n 0.000000\n \n \n 2\n 97.415167\n 16\n 2\n 0\n 16\n raw\n 133.447761\n 0.000000\n \n \n 3\n 96.857314\n 14\n 3\n 0\n 15\n raw\n 131.877318\n 0.000000\n \n \n 4\n 94.606208\n 17\n 3\n 0\n 48\n raw\n 128.785713\n 0.000000\n \n \n ...\n ...\n ...\n ...\n ...\n ...\n ...\n ...\n ...\n \n \n 195\n 0.346523\n 18\n 2\n 1\n 89\n raw\n -17.008649\n 0.979798\n \n \n 196\n 0.703782\n 15\n 3\n 1\n 82\n raw\n -17.292748\n 0.989899\n \n \n 197\n 0.058571\n 22\n 3\n 1\n 77\n raw\n -17.352293\n 1.000000\n \n \n 198\n 0.901983\n 9\n 2\n 1\n 64\n raw\n -17.357704\n 1.000000\n \n \n 199\n 0.320378\n 8\n 2\n 0\n 31\n raw\n -18.395421\n 1.000000\n \n \n\n200 rows × 8 columns\n\n\n\n\nperc.run_rerank_workflow(df, rerank_column='spec_idx')\n\n\n\n\n\n \n \n \n score\n nAA\n charge\n decoy\n spec_idx\n raw_name\n ml_score\n fdr\n \n \n \n \n 54\n 44.986000\n 25\n 2\n 0\n 0\n raw\n 23.239871\n 0.000000\n \n \n 6\n 94.020658\n 7\n 3\n 0\n 1\n raw\n 61.762973\n 0.000000\n \n \n 73\n 23.028068\n 14\n 2\n 0\n 2\n raw\n 5.346026\n 0.000000\n \n \n 17\n 79.163537\n 28\n 3\n 0\n 3\n raw\n 50.584693\n 0.000000\n \n \n 36\n 61.673923\n 23\n 2\n 0\n 4\n raw\n 36.500728\n 0.000000\n \n \n ...\n ...\n ...\n ...\n ...\n ...\n ...\n ...\n ...\n \n \n 170\n 2.306086\n 7\n 3\n 1\n 95\n raw\n -11.475920\n 0.744898\n \n \n 105\n 8.107192\n 8\n 2\n 1\n 96\n raw\n -6.765049\n 0.191011\n \n \n 92\n 9.717331\n 10\n 3\n 1\n 97\n raw\n -5.459666\n 0.044944\n \n \n 143\n 4.381494\n 29\n 3\n 1\n 98\n raw\n -9.100027\n 0.565217\n \n \n 130\n 5.831152\n 26\n 3\n 1\n 99\n raw\n -8.040386\n 0.423913\n \n \n\n100 rows × 8 columns" + }, + { + "objectID": "scoring/fdr.html", + "href": "scoring/fdr.html", + "title": "FDR functionalities", + "section": "", + "text": "In alphabase dataframes, we refer fdr values as q_values without loss of generacity.\n\n\nsource\n\ncalculate_fdr_from_ref\n\n calculate_fdr_from_ref (df:pandas.core.frame.DataFrame,\n ref_scores:numpy.ndarray,\n ref_fdr_values:numpy.ndarray, score_column:str,\n decoy_column:str='decoy')\n\nCalculate FDR values for a PSM dataframe from the given reference scores and fdr_values. It is used to extend peptide-level or sequence-level FDR (reference) to each PSM, as PSMs are more useful for quantification. ``\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ndf\nDataFrame\n\nPSM dataframe\n\n\nref_scores\nndarray\n\nreference scores that used to calculate ref_fdr_values, also sorted in decending order.\n\n\nref_fdr_values\nndarray\n\nfdr values corresponding to ref_scores\n\n\nscore_column\nstr\n\nscore column in the dataframe\n\n\ndecoy_column\nstr\ndecoy\ndecoy column in the dataframe. 1=target, 0=decoy. Defaults to ‘decoy’.\n\n\nReturns\nDataFrame\n\ndataframe with ‘fdr’ column added\n\n\n\n\nsource\n\n\nfdr_from_ref\n\n fdr_from_ref (sorted_scores:numpy.ndarray, ref_scores:numpy.ndarray,\n ref_fdr_values:numpy.ndarray)\n\nCalculate FDR values from the given reference scores and fdr_values. It is used to extend peptide-level or sequence-level FDR (reference) to each PSM, as PSMs are more useful for quantification.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nsorted_scores\nndarray\nthe scores to calculate FDRs, they must be sorted in decending order.\n\n\nref_scores\nndarray\nreference scores that used to calculate ref_fdr_values, also sorted in decending order.\n\n\nref_fdr_values\nndarray\nfdr values corresponding to ref_scores\n\n\nReturns\nndarray\nfdr values corresponding to sorted_scores.\n\n\n\n\nsource\n\n\ncalculate_fdr\n\n calculate_fdr (df:pandas.core.frame.DataFrame, score_column:str,\n decoy_column:str='decoy')\n\nCalculate FDR values (q_values in fact) for the given dataframe\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ndf\nDataFrame\n\nPSM dataframe to calculate FDRs\n\n\nscore_column\nstr\n\nscore column to sort in decending order\n\n\ndecoy_column\nstr\ndecoy\ndecoy column in the dataframe. 1=target, 0=decoy. Defaults to ‘decoy’.\n\n\nReturns\nDataFrame\n\nPSM dataframe with ‘fdr’ column added\n\n\n\n\nsource\n\n\nfdr_to_q_values\n\n fdr_to_q_values (fdr_values:numpy.ndarray)\n\nconvert FDR values to q_values.\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nfdr_values\nndarray\nFDR values, they should be sorted according to the descending order of the score\n\n\nReturns\nndarray\nq_values\n\n\n\n\ndf = pd.DataFrame(\n {\n 'score': np.random.random(500)*10+11,\n 'decoy': 0,\n 'kind': True,\n }\n)\nf_score = np.random.random(500)*9.9\ndf = df.append(\n pd.DataFrame(\n {\n 'score': f_score+0.01,\n 'decoy': 1,\n 'kind': False\n }\n )\n)\ndf = df.append(\n pd.DataFrame(\n {\n 'score': f_score,\n 'decoy': 0,\n 'kind': False\n }\n )\n)\ndf = df.append(\n pd.DataFrame(\n {\n 'score': np.random.random(5)+10,\n 'decoy': 1,\n 'kind': False\n }\n )\n)\n\ndf = calculate_fdr(df, 'score', 'decoy')\ndf\n\n\n\n\n\n \n \n \n score\n decoy\n kind\n fdr\n \n \n \n \n 417\n 20.986751\n 0\n True\n 0.000000\n \n \n 486\n 20.958803\n 0\n True\n 0.000000\n \n \n 46\n 20.954244\n 0\n True\n 0.000000\n \n \n 131\n 20.832440\n 0\n True\n 0.000000\n \n \n 236\n 20.809595\n 0\n True\n 0.000000\n \n \n ...\n ...\n ...\n ...\n ...\n \n \n 1111\n 0.046366\n 0\n False\n 0.504008\n \n \n 709\n 0.040841\n 1\n False\n 0.504505\n \n \n 1209\n 0.030841\n 0\n False\n 0.504505\n \n \n 939\n 0.013704\n 1\n False\n 0.505000\n \n \n 1439\n 0.003704\n 0\n False\n 0.505000\n \n \n\n1505 rows × 4 columns\n\n\n\n\ndf[(df.fdr < 0.01)&(df.decoy==0)]\n\n\n\n\n\n \n \n \n score\n decoy\n kind\n fdr\n \n \n \n \n 417\n 20.986751\n 0\n True\n 0.0\n \n \n 486\n 20.958803\n 0\n True\n 0.0\n \n \n 46\n 20.954244\n 0\n True\n 0.0\n \n \n 131\n 20.832440\n 0\n True\n 0.0\n \n \n 236\n 20.809595\n 0\n True\n 0.0\n \n \n ...\n ...\n ...\n ...\n ...\n \n \n 313\n 11.070695\n 0\n True\n 0.0\n \n \n 227\n 11.028431\n 0\n True\n 0.0\n \n \n 153\n 11.014330\n 0\n True\n 0.0\n \n \n 113\n 11.013978\n 0\n True\n 0.0\n \n \n 48\n 11.010629\n 0\n True\n 0.0\n \n \n\n500 rows × 4 columns" + }, { "objectID": "yaml_utils.html", "href": "yaml_utils.html", diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 675d7bc5..9c0e61a8 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,90 +2,106 @@ https://MannLabs.github.io/alphabase/utils.html - 2022-09-29T14:47:40.051Z + 2022-10-10T20:03:27.395Z + + + https://MannLabs.github.io/alphabase/statistics/regression.html + 2022-10-10T20:03:27.923Z https://MannLabs.github.io/alphabase/psm_reader/msfragger_reader.html - 2022-09-29T14:47:40.381Z + 2022-10-10T20:03:28.408Z https://MannLabs.github.io/alphabase/psm_reader/maxquant_reader.html - 2022-09-29T14:47:40.833Z + 2022-10-10T20:03:29.080Z https://MannLabs.github.io/alphabase/psm_reader/psm_reader.html - 2022-09-29T14:47:41.211Z + 2022-10-10T20:03:29.557Z https://MannLabs.github.io/alphabase/psm_reader/alphapept_reader.html - 2022-09-29T14:47:41.591Z + 2022-10-10T20:03:29.877Z https://MannLabs.github.io/alphabase/psm_reader/pfind_reader.html - 2022-09-29T14:47:41.874Z + 2022-10-10T20:03:30.257Z https://MannLabs.github.io/alphabase/psm_reader/dia_psm_reader.html - 2022-09-29T14:47:42.481Z + 2022-10-10T20:03:31.002Z https://MannLabs.github.io/alphabase/peptide/precursor.html - 2022-09-29T14:47:42.825Z + 2022-10-10T20:03:31.427Z https://MannLabs.github.io/alphabase/peptide/fragment.html - 2022-09-29T14:47:43.314Z + 2022-10-10T20:03:32.002Z https://MannLabs.github.io/alphabase/peptide/mass_calc.html - 2022-09-29T14:47:43.612Z + 2022-10-10T20:03:32.413Z https://MannLabs.github.io/alphabase/peptide/mobility.html - 2022-09-29T14:47:43.904Z + 2022-10-10T20:03:32.719Z https://MannLabs.github.io/alphabase/spectral_library/decoy_library.html - 2022-09-29T14:47:44.204Z + 2022-10-10T20:03:33.058Z https://MannLabs.github.io/alphabase/spectral_library/library_base.html - 2022-09-29T14:47:44.564Z + 2022-10-10T20:03:33.503Z https://MannLabs.github.io/alphabase/constants/modification.html - 2022-09-29T14:47:44.929Z + 2022-10-10T20:03:33.952Z https://MannLabs.github.io/alphabase/constants/element.html - 2022-09-29T14:47:45.267Z + 2022-10-10T20:03:34.291Z https://MannLabs.github.io/alphabase/constants/isotope.html - 2022-09-29T14:47:45.583Z + 2022-10-10T20:03:34.685Z https://MannLabs.github.io/alphabase/constants/aa.html - 2022-09-29T14:47:45.887Z + 2022-10-10T20:03:35.049Z https://MannLabs.github.io/alphabase/protein/fasta.html - 2022-09-29T14:47:46.453Z + 2022-10-10T20:03:35.816Z https://MannLabs.github.io/alphabase/protein/test_fasta.html - 2022-09-29T14:47:46.726Z + 2022-10-10T20:03:36.158Z https://MannLabs.github.io/alphabase/io/hdf.html - 2022-09-29T14:47:46.986Z + 2022-10-10T20:03:36.506Z + + + https://MannLabs.github.io/alphabase/scoring/feature_extraction_base.html + 2022-10-10T20:03:36.811Z + + + https://MannLabs.github.io/alphabase/scoring/ml_scoring_base.html + 2022-10-10T20:03:37.270Z + + + https://MannLabs.github.io/alphabase/scoring/fdr.html + 2022-10-10T20:03:37.687Z https://MannLabs.github.io/alphabase/yaml_utils.html - 2022-09-29T14:47:47.257Z + 2022-10-10T20:03:38.068Z https://MannLabs.github.io/alphabase/index.html - 2022-09-29T14:47:47.582Z + 2022-10-10T20:03:38.510Z diff --git a/docs/spectral_library/decoy_library.html b/docs/spectral_library/decoy_library.html index 79f74a46..3c730338 100644 --- a/docs/spectral_library/decoy_library.html +++ b/docs/spectral_library/decoy_library.html @@ -325,7 +325,7 @@

Decoy Libraries

+ + diff --git a/docs/spectral_library/library_base.html b/docs/spectral_library/library_base.html index f3120900..75e8327a 100644 --- a/docs/spectral_library/library_base.html +++ b/docs/spectral_library/library_base.html @@ -328,7 +328,7 @@

Base Class for Spectral Libraries

+ + diff --git a/docs/statistics/regression.html b/docs/statistics/regression.html new file mode 100644 index 00000000..c243268b --- /dev/null +++ b/docs/statistics/regression.html @@ -0,0 +1,655 @@ + + + + + + + + + +alphabase – regression + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + + + + +
+ + + +
+

Regression

+ +
+

source

+
+
+

LOESSRegression

+
+
 LOESSRegression (n_kernels:int=6, kernel_size:float=2.0,
+                  polynomial_degree:int=2)
+
+

scikit-learn estimator which implements a LOESS style local polynomial regression. The number of basis functions or kernels can be explicitly defined which allows for faster and cheaper training and inference.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
n_kernelsint6default = 6, The number of local polynomial functions used to approximate the data. The location and extend of the kernels will be distributed to contain an equal number of datapoints in the training set.
kernel_sizefloat2.0default = 2, A factor increasing the kernel size to overlap with the neighboring kernel.
polynomial_degreeint2default = 2, Degree of the polynomial functions used for the local approximation.
+
+

source

+
+
+

LOESSRegression.fit

+
+
 LOESSRegression.fit (x:numpy.ndarray, y:numpy.ndarray)
+
+

fit the model passed on provided training data.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
xndarrayfloat, of shape (n_samples,) or (n_samples, 1), Training data. Note that only a single feature is supported at the moment.
yndarrayof shape (n_samples,) or (n_samples, 1) Target values.
Returnsself: objectReturns the fitted estimator.
+
+

source

+
+
+

LOESSRegression.predict

+
+
 LOESSRegression.predict (x:numpy.ndarray)
+
+

Predict using the LOESS model.

+ +++++ + + + + + + + + + + + + + + + + + + + +
TypeDetails
xndarrayfloat, of shape (n_samples,) or (n_samples, 1) Feature data. Note that only a single feature is supported at the moment.
Returnsnumpy.ndarray, float
+
+
+

Application example

+
+
def noisy_1d(x):
+    y = np.sin(x)
+    y_err = np.random.normal(y,0.5)
+    return y + y_err + 0.5 * x
+
+x_train = np.linspace(0,15,200)
+y_train = noisy_1d(x_train)
+
+x_test = np.linspace(0,15,200)
+y_test = LOESSRegression().fit(x_train, y_train).predict(x_test)
+
+plt.scatter(x_train,y_train)
+plt.plot(x_test,y_test,c='r')
+plt.show()
+
+

+
+
+ + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/docs/statistics/regression_files/figure-html/cell-5-output-1.png b/docs/statistics/regression_files/figure-html/cell-5-output-1.png new file mode 100644 index 0000000000000000000000000000000000000000..9eca090ac3af87b332499959cb2e15936e93a428 GIT binary patch literal 28935 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 y1-w}Q-FEN4E;ms80Utils + + diff --git a/docs/yaml_utils.html b/docs/yaml_utils.html index 57c285c2..185e5eae 100644 --- a/docs/yaml_utils.html +++ b/docs/yaml_utils.html @@ -262,7 +262,7 @@

YAML Utils

+ + diff --git a/nbdev_nbs/psm_reader/dia_psm_reader.ipynb b/nbdev_nbs/psm_reader/dia_psm_reader.ipynb index 94a6003e..bca7cc76 100644 --- a/nbdev_nbs/psm_reader/dia_psm_reader.ipynb +++ b/nbdev_nbs/psm_reader/dia_psm_reader.ipynb @@ -1219,15 +1219,6 @@ "display_name": "Python 3.8.3 ('base')", "language": "python", "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.8.3" - }, - "vscode": { - "interpreter": { - "hash": "8a3b27e141e49c996c9b863f8707e97aabd49c4a7e8445b9b783b34e4a21a9b2" - } } }, "nbformat": 4, diff --git a/nbdev_nbs/scoring/fdr.ipynb b/nbdev_nbs/scoring/fdr.ipynb index 993a76ba..0adb3800 100644 --- a/nbdev_nbs/scoring/fdr.ipynb +++ b/nbdev_nbs/scoring/fdr.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -13,7 +13,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# FDR" + "# FDR functionalities" ] }, { @@ -27,7 +27,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -39,7 +39,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -206,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -336,7 +336,7 @@ "[1505 rows x 4 columns]" ] }, - "execution_count": 4, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -384,7 +384,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -514,7 +514,7 @@ "[500 rows x 4 columns]" ] }, - "execution_count": 5, + "execution_count": null, "metadata": {}, "output_type": "execute_result" } @@ -525,7 +525,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -535,7 +535,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -579,7 +579,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -603,15 +603,7 @@ "name": "python3" }, "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", "version": "3.8.3" }, "vscode": { diff --git a/nbdev_nbs/scoring/feature_extraction_base.ipynb b/nbdev_nbs/scoring/feature_extraction_base.ipynb index 258ce3f1..edff5132 100644 --- a/nbdev_nbs/scoring/feature_extraction_base.ipynb +++ b/nbdev_nbs/scoring/feature_extraction_base.ipynb @@ -62,18 +62,93 @@ " Extract the scoring features (self._feature_list) \n", " and append them inplace into candidate PSMs (psm_df).\n", "\n", + " **All sub-classes must re-implement this method.**\n", + "\n", " Parameters\n", " ----------\n", " psm_df : pd.DataFrame\n", - " PSMs to be rescore.\n", + " PSMs to be rescored\n", "\n", " Returns\n", " -------\n", " pd.DataFrame\n", - " psm_df with appended the feature list extracted by this extractor.\n", + " psm_df with appended feature columns extracted by this extractor\n", " \"\"\"\n", " return psm_df\n" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| hide\n", + "from nbdev.showdoc import show_doc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/feature_extraction_base.py#L30){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### BaseFeatureExtractor.extract_features\n", + "\n", + "> BaseFeatureExtractor.extract_features\n", + "> (psm_df:pandas.core.frame.DataFram\n", + "> e, *args, **kwargs)\n", + "\n", + "Extract the scoring features (self._feature_list) \n", + "and append them inplace into candidate PSMs (psm_df).\n", + "\n", + "**All sub-classes must re-implement this method.**\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| psm_df | DataFrame | PSMs to be rescored |\n", + "| args | | |\n", + "| kwargs | | |\n", + "| **Returns** | **DataFrame** | **psm_df with appended feature columns extracted by this extractor** |" + ], + "text/plain": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/feature_extraction_base.py#L30){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### BaseFeatureExtractor.extract_features\n", + "\n", + "> BaseFeatureExtractor.extract_features\n", + "> (psm_df:pandas.core.frame.DataFram\n", + "> e, *args, **kwargs)\n", + "\n", + "Extract the scoring features (self._feature_list) \n", + "and append them inplace into candidate PSMs (psm_df).\n", + "\n", + "**All sub-classes must re-implement this method.**\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| psm_df | DataFrame | PSMs to be rescored |\n", + "| args | | |\n", + "| kwargs | | |\n", + "| **Returns** | **DataFrame** | **psm_df with appended feature columns extracted by this extractor** |" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_doc(BaseFeatureExtractor.extract_features)" + ] } ], "metadata": { @@ -81,16 +156,6 @@ "display_name": "Python 3.8.3 ('base')", "language": "python", "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.8.3" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "8a3b27e141e49c996c9b863f8707e97aabd49c4a7e8445b9b783b34e4a21a9b2" - } } }, "nbformat": 4, diff --git a/nbdev_nbs/scoring/ml_scoring_base.ipynb b/nbdev_nbs/scoring/ml_scoring_base.ipynb index c0238437..080d44c4 100644 --- a/nbdev_nbs/scoring/ml_scoring_base.ipynb +++ b/nbdev_nbs/scoring/ml_scoring_base.ipynb @@ -106,7 +106,7 @@ "```python\n", "class DiaNNRescoring(Percolator):\n", " def _train(self, train_t_df, train_d_df):\n", - " # No target filtration on FDR, which is the same as DiaNN but different in Percolator\n", + " # No target filtration on FDR, which is the same as DiaNN but different from Percolator\n", " #train_t_df = train_t_df[train_t_df.fdr<=self.fdr]\n", " train_df = pd.concat((train_t_df, train_d_df))\n", " train_label = np.ones(len(train_df),dtype=np.int32)\n", @@ -117,7 +117,7 @@ " train_label\n", " )\n", " def rescore(self, psm_df):\n", - " # We don't need iteration anymore, but cross validation may be still necessary\n", + " # We don't need iteration anymore, but cross validation is still necessary\n", " df = self._cv_score(df)\n", " return self._estimate_fdr(df)\n", "```\n", @@ -147,26 +147,35 @@ " self.cv_fold = 1\n", " self.iter_num = 1\n", "\n", + " self._base_features = ['score','nAA','charge']\n", + "\n", " @property\n", " def feature_list(self)->list:\n", - " \"\"\" The read-only property to get extracted feature_list \"\"\"\n", - " return self.feature_extractor.feature_list\n", + " \"\"\" Get extracted feature_list. Property, read-only \"\"\"\n", + " return list(set(\n", + " self._base_features+\n", + " self.feature_extractor.feature_list\n", + " ))\n", "\n", " @property\n", " def ml_model(self):\n", + " \"\"\" \n", + " ML model in Percolator.\n", + " It can be sklearn models or other models but implement \n", + " the methods `fit()` and `decision_function()` (or `predict_proba()`) \n", + " which are the same as sklearn models.\n", + " \"\"\"\n", " return self._ml_model\n", " \n", " @ml_model.setter\n", " def ml_model(self, model):\n", - " \"\"\" \n", - " `model` must be sklearn models or other models but implement \n", - " the same methods `fit()` and `decision_function()`/`predict_proba()` \n", - " as sklearn models\n", - " \"\"\"\n", " self._ml_model = model\n", "\n", " @property\n", " def feature_extractor(self)->BaseFeatureExtractor:\n", + " \"\"\"\n", + " The feature extractor inherited from `BaseFeatureExtractor`\n", + " \"\"\"\n", " return self._feature_extractor\n", " \n", " @feature_extractor.setter\n", @@ -202,7 +211,8 @@ " def rescore(self, \n", " df:pd.DataFrame\n", " )->pd.DataFrame:\n", - " \"\"\"Rescore\n", + " \"\"\"\n", + " Estimate ML scores and then FDRs (q-values)\n", "\n", " Parameters\n", " ----------\n", @@ -220,7 +230,61 @@ " df = self._estimate_fdr(df)\n", " return df\n", "\n", - " def run(self,\n", + " def run_rerank_workflow(self,\n", + " top_k_psm_df:pd.DataFrame,\n", + " rerank_column:str='spec_idx',\n", + " *args, **kwargs\n", + " )->pd.DataFrame:\n", + " \"\"\"\n", + " Run percolator workflow with reranking \n", + " the peptides for each spectrum.\n", + "\n", + " - self.extract_features()\n", + " - self.rescore()\n", + "\n", + " *args and **kwargs are used for \n", + " `self.feature_extractor.extract_features`.\n", + "\n", + " Parameters\n", + " ----------\n", + " top_k_psm_df : pd.DataFrame\n", + " PSM DataFrame\n", + "\n", + " rerank_column : str\n", + " The column use to rerank PSMs. \n", + " \n", + " For example, use the following code to select \n", + " the top-ranked peptide for each spectrum.\n", + " ```\n", + " rerank_column = 'spec_idx' # scan_num\n", + " idx = top_k_psm_df.groupby(\n", + " ['raw_name',rerank_column]\n", + " )['ml_score'].idxmax()\n", + " psm_df = top_k_psm_df.loc[idx].copy()\n", + " ```\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " Only top-scored PSM is returned for \n", + " each group of the `rerank_column`.\n", + " \"\"\"\n", + " top_k_psm_df = self.extract_features(\n", + " top_k_psm_df, *args, **kwargs\n", + " )\n", + " idxmax = top_k_psm_df.groupby(\n", + " ['raw_name',rerank_column]\n", + " )['ml_score'].idxmax()\n", + "\n", + " df = top_k_psm_df.loc[idxmax].copy()\n", + " self._train_and_score(df)\n", + "\n", + " top_k_psm_df = self._predict(top_k_psm_df)\n", + " idxmax = top_k_psm_df.groupby(\n", + " ['raw_name',rerank_column]\n", + " )['ml_score'].idxmax()\n", + " return top_k_psm_df.loc[idxmax].copy()\n", + "\n", + " def run_rescore_workflow(self,\n", " psm_df:pd.DataFrame,\n", " *args, **kwargs\n", " )->pd.DataFrame:\n", @@ -228,7 +292,7 @@ " Run percolator workflow:\n", "\n", " - self.extract_features()\n", - " - self.re_score()\n", + " - self.rescore()\n", "\n", " *args and **kwargs are used for \n", " `self.feature_extractor.extract_features`.\n", @@ -314,12 +378,12 @@ " ):\n", " train_t_df = train_t_df[train_t_df.fdr<=self.fdr]\n", "\n", - " if len(train_t_df) > self.max_train_sample:\n", + " if len(train_t_df) > self.max_training_sample:\n", " train_t_df = train_t_df.sample(\n", " n=self.max_training_sample, \n", " random_state=1337\n", " )\n", - " if len(train_d_df) > self.max_train_sample:\n", + " if len(train_d_df) > self.max_training_sample:\n", " train_d_df = train_d_df.sample(\n", " n=self.max_training_sample,\n", " random_state=1337\n", @@ -345,6 +409,28 @@ " )\n", " return test_df\n", "\n", + " def _train_and_score(self,\n", + " df:pd.DataFrame\n", + " )->pd.DataFrame:\n", + "\n", + " df_target = df[df.decoy == 0]\n", + " df_decoy = df[df.decoy != 0]\n", + "\n", + " if (\n", + " np.sum(df_target.fdr<=self.fdr) < \n", + " self.min_training_sample or\n", + " len(df_decoy) < self.min_training_sample\n", + " ):\n", + " return df\n", + " \n", + " self._train(df_target, df_decoy)\n", + " test_df = pd.concat(\n", + " [df_target, df_decoy],\n", + " ignore_index=True\n", + " )\n", + " \n", + " return self._predict(test_df)\n", + "\n", " def _cv_score(self, df:pd.DataFrame)->pd.DataFrame:\n", " \"\"\"\n", " Apply cross-validation for rescoring.\n", @@ -363,9 +449,14 @@ " pd.DataFrame\n", " PSMs after rescoring\n", " \"\"\"\n", + "\n", + " if self.cv_fold <= 1:\n", + " return self._train_and_score(df)\n", + "\n", " df = df.sample(\n", " frac=1, random_state=1337\n", " ).reset_index(drop=True)\n", + "\n", " df_target = df[df.decoy == 0]\n", " df_decoy = df[df.decoy != 0]\n", "\n", @@ -377,36 +468,826 @@ " ):\n", " return df\n", " \n", - " if self.cv_fold > 1:\n", - " test_df_list = []\n", - " for i in range(self.cv_fold):\n", - " t_mask = np.ones(len(df_target), dtype=bool)\n", - " _slice = slice(i, len(df_target), self.cv_fold)\n", - " t_mask[_slice] = False\n", - " train_t_df = df_target[t_mask]\n", - " test_t_df = df_target[_slice]\n", - " \n", - " d_mask = np.ones(len(df_decoy), dtype=bool)\n", - " _slice = slice(i, len(df_decoy), self.cv_fold)\n", - " d_mask[_slice] = False\n", - " train_d_df = df_decoy[d_mask]\n", - " test_d_df = df_decoy[_slice]\n", - "\n", - " self._train(train_t_df, train_d_df)\n", - "\n", - " test_df = pd.concat((test_t_df, test_d_df))\n", - " test_df_list.append(self._predict(test_df))\n", - " \n", - " return pd.concat(test_df_list, ignore_index=True)\n", - " else:\n", + " test_df_list = []\n", + " for i in range(self.cv_fold):\n", + " t_mask = np.ones(len(df_target), dtype=bool)\n", + " _slice = slice(i, len(df_target), self.cv_fold)\n", + " t_mask[_slice] = False\n", + " train_t_df = df_target[t_mask]\n", + " test_t_df = df_target[_slice]\n", + " \n", + " d_mask = np.ones(len(df_decoy), dtype=bool)\n", + " _slice = slice(i, len(df_decoy), self.cv_fold)\n", + " d_mask[_slice] = False\n", + " train_d_df = df_decoy[d_mask]\n", + " test_d_df = df_decoy[_slice]\n", "\n", - " self._train(df_target, df_decoy)\n", - " test_df = pd.concat((df_target, df_decoy),ignore_index=True)\n", - " \n", - " return self._predict(test_df)\n", + " self._train(train_t_df, train_d_df)\n", + "\n", + " test_df = pd.concat((test_t_df, test_d_df))\n", + " test_df_list.append(self._predict(test_df))\n", + " \n", + " return pd.concat(test_df_list, ignore_index=True)\n", " " ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| hide\n", + "from nbdev.showdoc import show_doc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Properties of `Percolator`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L46){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.ml_model\n", + "\n", + "> Percolator.ml_model ()\n", + "\n", + "ML model in Percolator.\n", + "It can be sklearn models or other models but implement \n", + "the methods `fit()` and `decision_function()` (or `predict_proba()`) \n", + "which are the same as sklearn models." + ], + "text/plain": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L46){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.ml_model\n", + "\n", + "> Percolator.ml_model ()\n", + "\n", + "ML model in Percolator.\n", + "It can be sklearn models or other models but implement \n", + "the methods `fit()` and `decision_function()` (or `predict_proba()`) \n", + "which are the same as sklearn models." + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_doc(Percolator.ml_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L66){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.feature_extractor\n", + "\n", + "> Percolator.feature_extractor ()\n", + "\n", + "The feature extractor inherited from `BaseFeatureExtractor`" + ], + "text/plain": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L66){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.feature_extractor\n", + "\n", + "> Percolator.feature_extractor ()\n", + "\n", + "The feature extractor inherited from `BaseFeatureExtractor`" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_doc(Percolator.feature_extractor)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L37){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.feature_list\n", + "\n", + "> Percolator.feature_list ()\n", + "\n", + "Get extracted feature_list. Property, read-only" + ], + "text/plain": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L37){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.feature_list\n", + "\n", + "> Percolator.feature_list ()\n", + "\n", + "Get extracted feature_list. Property, read-only" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_doc(Percolator.feature_list)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Methods of `Percolator`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L69){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.extract_features\n", + "\n", + "> Percolator.extract_features (psm_df:pandas.core.frame.DataFrame, *args,\n", + "> **kwargs)\n", + "\n", + "Extract features for rescoring.\n", + "\n", + "*args and **kwargs are used for \n", + "`self.feature_extractor.extract_features`.\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| psm_df | DataFrame | PSM DataFrame |\n", + "| args | | |\n", + "| kwargs | | |\n", + "| **Returns** | **DataFrame** | **psm_df with feature columns appended inplace.** |" + ], + "text/plain": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L69){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.extract_features\n", + "\n", + "> Percolator.extract_features (psm_df:pandas.core.frame.DataFrame, *args,\n", + "> **kwargs)\n", + "\n", + "Extract features for rescoring.\n", + "\n", + "*args and **kwargs are used for \n", + "`self.feature_extractor.extract_features`.\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| psm_df | DataFrame | PSM DataFrame |\n", + "| args | | |\n", + "| kwargs | | |\n", + "| **Returns** | **DataFrame** | **psm_df with feature columns appended inplace.** |" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_doc(Percolator.extract_features)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L95){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.rescore\n", + "\n", + "> Percolator.rescore (df:pandas.core.frame.DataFrame)\n", + "\n", + "Estimate ML scores and then FDRs (q-values)\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| df | DataFrame | psm_df |\n", + "| **Returns** | **DataFrame** | **psm_df with `ml_score` and `fdr` columns updated inplace** |" + ], + "text/plain": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L95){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.rescore\n", + "\n", + "> Percolator.rescore (df:pandas.core.frame.DataFrame)\n", + "\n", + "Estimate ML scores and then FDRs (q-values)\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| df | DataFrame | psm_df |\n", + "| **Returns** | **DataFrame** | **psm_df with `ml_score` and `fdr` columns updated inplace** |" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_doc(Percolator.rescore)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L171){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.run_rescore_workflow\n", + "\n", + "> Percolator.run_rescore_workflow (psm_df:pandas.core.frame.DataFrame,\n", + "> *args, **kwargs)\n", + "\n", + "Run percolator workflow:\n", + "\n", + "- self.extract_features()\n", + "- self.rescore()\n", + "\n", + "*args and **kwargs are used for \n", + "`self.feature_extractor.extract_features`.\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| psm_df | DataFrame | PSM DataFrame |\n", + "| args | | |\n", + "| kwargs | | |\n", + "| **Returns** | **DataFrame** | **psm_df with feature columns appended inplace.** |" + ], + "text/plain": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L171){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.run_rescore_workflow\n", + "\n", + "> Percolator.run_rescore_workflow (psm_df:pandas.core.frame.DataFrame,\n", + "> *args, **kwargs)\n", + "\n", + "Run percolator workflow:\n", + "\n", + "- self.extract_features()\n", + "- self.rescore()\n", + "\n", + "*args and **kwargs are used for \n", + "`self.feature_extractor.extract_features`.\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| psm_df | DataFrame | PSM DataFrame |\n", + "| args | | |\n", + "| kwargs | | |\n", + "| **Returns** | **DataFrame** | **psm_df with feature columns appended inplace.** |" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_doc(Percolator.run_rescore_workflow)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L117){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.run_rerank_workflow\n", + "\n", + "> Percolator.run_rerank_workflow (top_k_psm_df:pandas.core.frame.DataFrame,\n", + "> rerank_column:str='spec_idx', *args,\n", + "> **kwargs)\n", + "\n", + "Run percolator workflow with reranking \n", + "the peptides for each spectrum.\n", + "\n", + "- self.extract_features()\n", + "- self.rescore()\n", + "\n", + "*args and **kwargs are used for \n", + "`self.feature_extractor.extract_features`.\n", + "\n", + "| | **Type** | **Default** | **Details** |\n", + "| -- | -------- | ----------- | ----------- |\n", + "| top_k_psm_df | DataFrame | | PSM DataFrame |\n", + "| rerank_column | str | spec_idx | The column use to rerank PSMs.

For example, use the following code to select
the top-ranked peptide for each spectrum.
```
rerank_column = 'spec_idx' # scan_num
idx = top_k_psm_df.groupby(
['raw_name',rerank_column]
)['ml_score'].idxmax()
psm_df = top_k_psm_df.loc[idx].copy()
``` |\n", + "| args | | | |\n", + "| kwargs | | | |\n", + "| **Returns** | **DataFrame** | | |" + ], + "text/plain": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/ml_scoring_base.py#L117){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### Percolator.run_rerank_workflow\n", + "\n", + "> Percolator.run_rerank_workflow (top_k_psm_df:pandas.core.frame.DataFrame,\n", + "> rerank_column:str='spec_idx', *args,\n", + "> **kwargs)\n", + "\n", + "Run percolator workflow with reranking \n", + "the peptides for each spectrum.\n", + "\n", + "- self.extract_features()\n", + "- self.rescore()\n", + "\n", + "*args and **kwargs are used for \n", + "`self.feature_extractor.extract_features`.\n", + "\n", + "| | **Type** | **Default** | **Details** |\n", + "| -- | -------- | ----------- | ----------- |\n", + "| top_k_psm_df | DataFrame | | PSM DataFrame |\n", + "| rerank_column | str | spec_idx | The column use to rerank PSMs.

For example, use the following code to select
the top-ranked peptide for each spectrum.
```
rerank_column = 'spec_idx' # scan_num
idx = top_k_psm_df.groupby(
['raw_name',rerank_column]
)['ml_score'].idxmax()
psm_df = top_k_psm_df.loc[idx].copy()
``` |\n", + "| args | | | |\n", + "| kwargs | | | |\n", + "| **Returns** | **DataFrame** | | |" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_doc(Percolator.run_rerank_workflow)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple Examples" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
scorenAAchargedecoyspec_idxraw_nameml_scorefdr
099.851979263018raw138.1427660.000000
198.74605273012raw133.8677790.000000
297.415167162016raw133.4477610.000000
396.857314143015raw131.8773180.000000
494.606208173048raw128.7857130.000000
...........................
1950.346523182189raw-17.0086490.979798
1960.703782153182raw-17.2927480.989899
1970.058571223177raw-17.3522931.000000
1980.90198392164raw-17.3577041.000000
1990.32037882031raw-18.3954211.000000
\n", + "

200 rows × 8 columns

\n", + "
" + ], + "text/plain": [ + " score nAA charge decoy spec_idx raw_name ml_score fdr\n", + "0 99.851979 26 3 0 18 raw 138.142766 0.000000\n", + "1 98.746052 7 3 0 12 raw 133.867779 0.000000\n", + "2 97.415167 16 2 0 16 raw 133.447761 0.000000\n", + "3 96.857314 14 3 0 15 raw 131.877318 0.000000\n", + "4 94.606208 17 3 0 48 raw 128.785713 0.000000\n", + ".. ... ... ... ... ... ... ... ...\n", + "195 0.346523 18 2 1 89 raw -17.008649 0.979798\n", + "196 0.703782 15 3 1 82 raw -17.292748 0.989899\n", + "197 0.058571 22 3 1 77 raw -17.352293 1.000000\n", + "198 0.901983 9 2 1 64 raw -17.357704 1.000000\n", + "199 0.320378 8 2 0 31 raw -18.395421 1.000000\n", + "\n", + "[200 rows x 8 columns]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = pd.DataFrame({\n", + " 'score': list(np.random.uniform(0,100,100))+list(np.random.uniform(0,10,100)),\n", + " 'nAA': list(np.random.randint(7,30,200)),\n", + " 'charge': list(np.random.randint(2,4,200)),\n", + " 'decoy': [0]*100+[1]*100,\n", + " 'spec_idx': np.repeat(np.arange(100),2),\n", + " 'raw_name': 'raw',\n", + "})\n", + "perc = Percolator()\n", + "perc.min_training_sample = 10\n", + "perc.run_rescore_workflow(df)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
scorenAAchargedecoyspec_idxraw_nameml_scorefdr
5444.98600025200raw23.2398710.000000
694.0206587301raw61.7629730.000000
7323.02806814202raw5.3460260.000000
1779.16353728303raw50.5846930.000000
3661.67392323204raw36.5007280.000000
...........................
1702.30608673195raw-11.4759200.744898
1058.10719282196raw-6.7650490.191011
929.717331103197raw-5.4596660.044944
1434.381494293198raw-9.1000270.565217
1305.831152263199raw-8.0403860.423913
\n", + "

100 rows × 8 columns

\n", + "
" + ], + "text/plain": [ + " score nAA charge decoy spec_idx raw_name ml_score fdr\n", + "54 44.986000 25 2 0 0 raw 23.239871 0.000000\n", + "6 94.020658 7 3 0 1 raw 61.762973 0.000000\n", + "73 23.028068 14 2 0 2 raw 5.346026 0.000000\n", + "17 79.163537 28 3 0 3 raw 50.584693 0.000000\n", + "36 61.673923 23 2 0 4 raw 36.500728 0.000000\n", + ".. ... ... ... ... ... ... ... ...\n", + "170 2.306086 7 3 1 95 raw -11.475920 0.744898\n", + "105 8.107192 8 2 1 96 raw -6.765049 0.191011\n", + "92 9.717331 10 3 1 97 raw -5.459666 0.044944\n", + "143 4.381494 29 3 1 98 raw -9.100027 0.565217\n", + "130 5.831152 26 3 1 99 raw -8.040386 0.423913\n", + "\n", + "[100 rows x 8 columns]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "perc.run_rerank_workflow(df, rerank_column='spec_idx')" + ] + }, { "cell_type": "code", "execution_count": null, @@ -420,24 +1301,6 @@ "display_name": "Python 3.8.3 ('base')", "language": "python", "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.3" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "8a3b27e141e49c996c9b863f8707e97aabd49c4a7e8445b9b783b34e4a21a9b2" - } } }, "nbformat": 4, diff --git a/nbdev_nbs/sidebar.yml b/nbdev_nbs/sidebar.yml index b4c2cebb..53028637 100644 --- a/nbdev_nbs/sidebar.yml +++ b/nbdev_nbs/sidebar.yml @@ -31,7 +31,15 @@ website: - psm_reader/msfragger_reader.ipynb - psm_reader/pfind_reader.ipynb - psm_reader/psm_reader.ipynb + - section: scoring + contents: + - scoring/fdr.ipynb + - scoring/feature_extraction_base.ipynb + - scoring/ml_scoring_base.ipynb - section: spectral_library contents: - spectral_library/decoy_library.ipynb - - spectral_library/library_base.ipynb \ No newline at end of file + - spectral_library/library_base.ipynb + - section: statistics + contents: + - statistics/regression.ipynb \ No newline at end of file From 5f06b3ce5b3c3f81db2ab4bcb010edb6185c03ca Mon Sep 17 00:00:00 2001 From: GeorgWa Date: Wed, 12 Oct 2022 11:45:33 +0200 Subject: [PATCH 09/52] updated weight function --- alphabase/_modidx.py | 10 +- alphabase/statistics/regression.py | 66 +++++++----- nbdev_nbs/_quarto.yml | 3 - nbdev_nbs/sidebar.yml | 5 +- nbdev_nbs/statistics/regression.ipynb | 140 ++++++++++++++++++++------ 5 files changed, 165 insertions(+), 59 deletions(-) diff --git a/alphabase/_modidx.py b/alphabase/_modidx.py index bd864a2f..34016410 100644 --- a/alphabase/_modidx.py +++ b/alphabase/_modidx.py @@ -493,8 +493,14 @@ 'alphabase/statistics/regression.py'), 'alphabase.statistics.regression.LOESSRegression.set_params': ( 'statistics/regression.html#loessregression.set_params', 'alphabase/statistics/regression.py'), - 'alphabase.statistics.regression.LOESSRegression.tricubic': ( 'statistics/regression.html#loessregression.tricubic', - 'alphabase/statistics/regression.py')}, + 'alphabase.statistics.regression.apply_kernel': ( 'statistics/regression.html#apply_kernel', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.left_open_tricubic': ( 'statistics/regression.html#left_open_tricubic', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.right_open_tricubic': ( 'statistics/regression.html#right_open_tricubic', + 'alphabase/statistics/regression.py'), + 'alphabase.statistics.regression.tricubic': ( 'statistics/regression.html#tricubic', + 'alphabase/statistics/regression.py')}, 'alphabase.utils': { 'alphabase.utils._flatten': ('utils.html#_flatten', 'alphabase/utils.py'), 'alphabase.utils.explode_multiple_columns': ('utils.html#explode_multiple_columns', 'alphabase/utils.py'), 'alphabase.utils.process_bar': ('utils.html#process_bar', 'alphabase/utils.py')}, diff --git a/alphabase/statistics/regression.py b/alphabase/statistics/regression.py index ad457386..dcbe61cf 100644 --- a/alphabase/statistics/regression.py +++ b/alphabase/statistics/regression.py @@ -1,7 +1,7 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbdev_nbs/statistics/regression.ipynb. # %% auto 0 -__all__ = ['EPSILON', 'LOESSRegression'] +__all__ = ['EPSILON', 'LOESSRegression', 'apply_kernel', 'tricubic', 'left_open_tricubic', 'right_open_tricubic'] # %% ../../nbdev_nbs/statistics/regression.ipynb 2 import numpy as np @@ -231,36 +231,54 @@ def get_weight_matrix(self, x: np.ndarray): """ w = np.tile(x,(1,self.n_kernels)) - w = np.abs(w - self.scale_mean) + w = w - self.scale_mean w = w/self.scale_max - + # apply weighting kernel - w = self.tricubic(w) + w = apply_kernel(w) + + w = w/np.sum(w, axis=1, keepdims=True) + + return w - #perform epsilon padding at the start and end of the weight matrix to allow for extrapolation. - # START - idx_values = np.where(w[:,0] > 0)[0] - if len(idx_values) > 0: - min_idx, max_idx = idx_values[[0, -1]] - w[:min_idx,0] = EPSILON - # END - idx_values = np.where(w[:,-1] > 0)[0] - if len(idx_values) > 0: - min_idx, max_idx = idx_values[[0, -1]] - w[max_idx:,-1] = EPSILON +def apply_kernel(w): - # normalize column wise + num_cols = w.shape[1] - w = w/np.sum(w, axis=1, keepdims=True) + if num_cols == 1: + return np.ones(w.shape) + + if num_cols == 2: + w[:,0] = left_open_tricubic(w[:,0]) + w[:,1] = right_open_tricubic(w[:,1]) return w - - @staticmethod - def tricubic(x): - """tricubic weight kernel""" - epsilon = EPSILON - mask = np.abs(x) <= 1 - return mask * (np.power(1-np.power(np.abs(x),3),3) + epsilon) + if num_cols > 2 : + w[:,0] = left_open_tricubic(w[:,0]) + w[:,1:-1] = tricubic(w[:,1:-1]) + w[:,-1] = right_open_tricubic(w[:,-1]) + + return w + +def tricubic(x): + """tricubic weight kernel""" + epsilon = EPSILON + mask = np.abs(x) <= 1 + return mask * (np.power(1-np.power(np.abs(x),3),3) + epsilon) + + +def left_open_tricubic(x): + """tricubic weight kernel which weights assigns 1 to values x < 0""" + y = tricubic(x) + y[x < 0] = 1 + return y + + +def right_open_tricubic(x): + """tricubic weight kernel which weights assigns 1 to values x > 0""" + y = tricubic(x) + y[x > 0] = 1 + return y diff --git a/nbdev_nbs/_quarto.yml b/nbdev_nbs/_quarto.yml index 7ffb44c1..0a6dfcb2 100644 --- a/nbdev_nbs/_quarto.yml +++ b/nbdev_nbs/_quarto.yml @@ -14,9 +14,6 @@ website: navbar: background: primary search: true - right: - - icon: github - href: "https://github.com/MannLabs/alphabase" sidebar: style: floating diff --git a/nbdev_nbs/sidebar.yml b/nbdev_nbs/sidebar.yml index b4c2cebb..32d574b8 100644 --- a/nbdev_nbs/sidebar.yml +++ b/nbdev_nbs/sidebar.yml @@ -34,4 +34,7 @@ website: - section: spectral_library contents: - spectral_library/decoy_library.ipynb - - spectral_library/library_base.ipynb \ No newline at end of file + - spectral_library/library_base.ipynb + - section: statistics + contents: + - statistics/regression.ipynb \ No newline at end of file diff --git a/nbdev_nbs/statistics/regression.ipynb b/nbdev_nbs/statistics/regression.ipynb index fbcdcc99..07c97a85 100644 --- a/nbdev_nbs/statistics/regression.ipynb +++ b/nbdev_nbs/statistics/regression.ipynb @@ -20,7 +20,16 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/georgwallmann/miniconda3/envs/alphadia/lib/python3.8/site-packages/scipy/__init__.py:146: UserWarning: A NumPy version >=1.16.5 and <1.23.0 is required for this version of SciPy (detected version 1.23.1\n", + " warnings.warn(f\"A NumPy version >={np_minversion} and <{np_maxversion}\"\n" + ] + } + ], "source": [ "#| export\n", "import numpy as np\n", @@ -267,38 +276,56 @@ " \"\"\"\n", " w = np.tile(x,(1,self.n_kernels))\n", "\n", - " w = np.abs(w - self.scale_mean)\n", + " w = w - self.scale_mean\n", " w = w/self.scale_max\n", - " \n", + "\n", " # apply weighting kernel\n", - " w = self.tricubic(w)\n", + " w = apply_kernel(w)\n", + "\n", + " w = w/np.sum(w, axis=1, keepdims=True)\n", + "\n", + " return w\n", " \n", - " #perform epsilon padding at the start and end of the weight matrix to allow for extrapolation.\n", - " # START\n", - " idx_values = np.where(w[:,0] > 0)[0]\n", - " if len(idx_values) > 0:\n", - " min_idx, max_idx = idx_values[[0, -1]]\n", - " w[:min_idx,0] = EPSILON\n", "\n", - " # END\n", - " idx_values = np.where(w[:,-1] > 0)[0]\n", - " if len(idx_values) > 0:\n", - " min_idx, max_idx = idx_values[[0, -1]]\n", - " w[max_idx:,-1] = EPSILON\n", + "def apply_kernel(w):\n", "\n", - " # normalize column wise\n", + " num_cols = w.shape[1]\n", "\n", - " w = w/np.sum(w, axis=1, keepdims=True)\n", + " if num_cols == 1:\n", + " return np.ones(w.shape)\n", + "\n", + " if num_cols == 2:\n", + " w[:,0] = left_open_tricubic(w[:,0])\n", + " w[:,1] = right_open_tricubic(w[:,1])\n", "\n", " return w\n", - " \n", "\n", - " @staticmethod\n", - " def tricubic(x):\n", - " \"\"\"tricubic weight kernel\"\"\"\n", - " epsilon = EPSILON\n", - " mask = np.abs(x) <= 1\n", - " return mask * (np.power(1-np.power(np.abs(x),3),3) + epsilon)\n", + " if num_cols > 2 :\n", + " w[:,0] = left_open_tricubic(w[:,0])\n", + " w[:,1:-1] = tricubic(w[:,1:-1])\n", + " w[:,-1] = right_open_tricubic(w[:,-1])\n", + "\n", + " return w\n", + " \n", + "def tricubic(x):\n", + " \"\"\"tricubic weight kernel\"\"\"\n", + " epsilon = EPSILON\n", + " mask = np.abs(x) <= 1\n", + " return mask * (np.power(1-np.power(np.abs(x),3),3) + epsilon)\n", + "\n", + "\n", + "def left_open_tricubic(x):\n", + " \"\"\"tricubic weight kernel which weights assigns 1 to values x < 0\"\"\"\n", + " y = tricubic(x)\n", + " y[x < 0] = 1\n", + " return y\n", + "\n", + "\n", + "def right_open_tricubic(x):\n", + " \"\"\"tricubic weight kernel which weights assigns 1 to values x > 0\"\"\"\n", + " y = tricubic(x)\n", + " y[x > 0] = 1\n", + " return y\n", " " ] }, @@ -373,7 +400,7 @@ "text/markdown": [ "---\n", "\n", - "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/statistics/regression.py#L156){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/statistics/regression.py#L184){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### LOESSRegression.predict\n", "\n", @@ -389,7 +416,7 @@ "text/plain": [ "---\n", "\n", - "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/statistics/regression.py#L156){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/statistics/regression.py#L184){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### LOESSRegression.predict\n", "\n", @@ -445,7 +472,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -455,7 +482,7 @@ } ], "source": [ - "\n", + "np.set_printoptions(formatter={'float': lambda x: \"{0:0.3f}\".format(x)})\n", "def noisy_1d(x):\n", " y = np.sin(x)\n", " y_err = np.random.normal(y,0.5)\n", @@ -472,6 +499,51 @@ "plt.show()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Weight function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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SqUSixcvRk5ODlQqFR588EE0b97cqPXMmTMH3333HUaNGoXJkycjODgYWVlZ+P7777Fq1SoEBgaiU6dO6N27N6KiopCVlQUXFxds2LABZWVlVa7T09MTixcvRlJSEtq1a4eNGzciPj4ea9asuePgyB999BHuv/9+dO/eHdOnT4efnx+SkpKwc+dOxMfHG7VfRHSPZLwjl4hkUDE0SUZGRpXz/y4/P1/MnDlTuLi4CHt7ezFs2DBx/vx5AUC8++67d11nVcNtnDt3TvTt21fY2toKAEYNfZKbmyteeukl4enpKaytrYW/v794//33Kw2rAUDMnDlTfPXVV8Lf31+oVCrRrVu3KocYSU9PFzNnzhTe3t7C2tpaeHh4iIEDB4o1a9bo21QMD7J582aDZasa+qMmPv30U9G6dWuhVCoNhh3x8fERgwcPrnKZfw53IoQQt27dErNmzRItW7YUNjY2wsvLS0yYMMFg+JbLly+LsLAwoVKphLu7u5g7d67Yt29flcOddOrUSRw/flyEhoYKtVotfHx8xIoVK2q0z6dPnxbDhw8Xzs7OQq1Wi4CAAPH6668b1S9EdO8kIXisnIhqLj4+Ht26dcNXX32Fp556Su5yqiRJEmbOnFnptC0RkaXjNXZEVK2K55T+3fLly6FQKNC3b18ZKiIiojvhNXZEVK333nsPsbGxGDBgAKysrLB7927s3r0b06dPh7e3d51sQ6vV3vVGBXt7e9jb29fJ9upLYWEhcnJy7tjGxcXFYIBjIqK6xmBHRNXq06cP9u3bh7feegt5eXlo1aoVFixYUKcPd09JSbnroLzz58/HggUL6myb9WHjxo36gY2rc+DAAfTv379hCiKiRonX2BGRrIqKivDrr7/esU3r1q3146WZqhs3biAhIeGObYKDg/VjyxER1QcGOyIiIiILYRanYnU6Ha5fvw4HBwc+VJqIiIgaFSEEcnNz4enpCYXizve9mkWwu379ep1dqE1ERERkjlJSUuDl5XXHNkYHu8OHD+P9999HbGwsbty4gW3btmHYsGF3XObgwYOIjIxEQkICvL298dprr2HixIk13mbFcyFTUlLg6OhobMlEREREZkuj0cDb29vgOdnVMTrY5efnIzAwEJMnT8aIESPu2j4xMRGDBw/Gs88+i6+//hrR0dGYOnUqWrRooX8W5d1UnH51dHRksCMi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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axs = plt.subplots(nrows=3,sharex=True)\n", + "\n", + "x = np.linspace(-2,2,200)\n", + "y = left_open_tricubic(x)\n", + "axs[0].plot(x,y)\n", + "axs[0].set_title('left_open_tricubic')\n", + "\n", + "x = np.linspace(-2,2,200)\n", + "y = tricubic(x)\n", + "axs[1].plot(x,y)\n", + "axs[1].set_title('tricubic')\n", + "\n", + "x = np.linspace(-2,2,200)\n", + "y = right_open_tricubic(x)\n", + "axs[2].plot(x,y)\n", + "axs[2].set_title('right_open_tricubic')\n", + "\n", + "fig.tight_layout()\n", + "plt.show()" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -516,12 +588,22 @@ "x_test = np.linspace(-10,25,10)\n", "y_test = LOESSRegression(n_kernels=4, polynomial_degree=2).fit(x_train, y_train).predict(x_test)\n", "\n", + "\n", + "\n", "# single datapoint inference\n", "\n", "x_train = np.linspace(0,15,100)\n", "y_train = noisy_1d(x_train)\n", "x_test = np.linspace(10,10,1)\n", - "y_test = LOESSRegression(n_kernels=4, polynomial_degree=2).fit(x_train, y_train).predict(x_test)" + "y_test = LOESSRegression(n_kernels=4, polynomial_degree=2).fit(x_train, y_train).predict(x_test)\n", + "\n", + "# extrapolation when no other points are interpolated\n", + "\n", + "x_train = np.linspace(0,15,60)\n", + "y_train = y = np.sin(x_train)\n", + "x_test = np.linspace(-2,-1,1)\n", + "y_test = LOESSRegression(n_kernels=4, polynomial_degree=2).fit(x_train, y_train).predict(x_test)\n", + "assert (-2.604 - y_test) < 0.01" ] } ], From b653ecaa8009e9187c3358f22d81d1d56e85a529 Mon Sep 17 00:00:00 2001 From: "Zeng, Wen-Feng" Date: Wed, 12 Oct 2022 13:56:54 +0200 Subject: [PATCH 10/52] FE:update_features --- alphabase/_modidx.py | 4 +- alphabase/scoring/feature_extraction_base.py | 17 +++++ alphabase/scoring/ml_scoring_base.py | 9 +-- nbdev_nbs/scoring/fdr.ipynb | 9 --- .../scoring/feature_extraction_base.ipynb | 68 +++++++++++++++++++ nbdev_nbs/scoring/ml_scoring_base.ipynb | 18 +++-- 6 files changed, 101 insertions(+), 24 deletions(-) diff --git a/alphabase/_modidx.py b/alphabase/_modidx.py index 8f4978ef..66b0f76b 100644 --- a/alphabase/_modidx.py +++ b/alphabase/_modidx.py @@ -401,7 +401,9 @@ 'alphabase.scoring.feature_extraction_base.BaseFeatureExtractor.extract_features': ( 'scoring/feature_extraction_base.html#basefeatureextractor.extract_features', 'alphabase/scoring/feature_extraction_base.py'), 'alphabase.scoring.feature_extraction_base.BaseFeatureExtractor.feature_list': ( 'scoring/feature_extraction_base.html#basefeatureextractor.feature_list', - 'alphabase/scoring/feature_extraction_base.py')}, + 'alphabase/scoring/feature_extraction_base.py'), + 'alphabase.scoring.feature_extraction_base.BaseFeatureExtractor.update_features': ( 'scoring/feature_extraction_base.html#basefeatureextractor.update_features', + 'alphabase/scoring/feature_extraction_base.py')}, 'alphabase.scoring.ml_scoring_base': { 'alphabase.scoring.ml_scoring_base.Percolator': ( 'scoring/ml_scoring_base.html#percolator', 'alphabase/scoring/ml_scoring_base.py'), 'alphabase.scoring.ml_scoring_base.Percolator.__init__': ( 'scoring/ml_scoring_base.html#percolator.__init__', diff --git a/alphabase/scoring/feature_extraction_base.py b/alphabase/scoring/feature_extraction_base.py index 5d624c5f..1916e81f 100644 --- a/alphabase/scoring/feature_extraction_base.py +++ b/alphabase/scoring/feature_extraction_base.py @@ -49,3 +49,20 @@ def extract_features(self, """ return psm_df + def update_features(self,psm_df:pd.DataFrame)->pd.DataFrame: + """ + This method allow us to update adaptive features + during the iteration of Percolator algorithm + + Parameters + ---------- + psm_df : pd.DataFrame + psm_df + + Returns + ------- + pd.DataFrame + psm_df with updated feature values + """ + return psm_df + diff --git a/alphabase/scoring/ml_scoring_base.py b/alphabase/scoring/ml_scoring_base.py index 5bcbe809..b04ad74e 100644 --- a/alphabase/scoring/ml_scoring_base.py +++ b/alphabase/scoring/ml_scoring_base.py @@ -25,7 +25,7 @@ def __init__(self): self._ml_model = LogisticRegression() self.fdr_level = 'psm' # psm, precursor, peptide, or sequence - self.fdr = 0.01 + self.training_fdr = 0.01 self.per_raw_fdr = False self.max_training_sample = 200000 @@ -113,6 +113,7 @@ def rescore(self, for i in range(self.iter_num): df = self._cv_score(df) df = self._estimate_fdr(df, 'psm', False) + df = self.feature_extractor.update_features(df) df = self._estimate_fdr(df) return df @@ -262,7 +263,7 @@ def _train(self, train_t_df:pd.DataFrame, train_d_df:pd.DataFrame ): - train_t_df = train_t_df[train_t_df.fdr<=self.fdr] + train_t_df = train_t_df[train_t_df.fdr<=self.training_fdr] if len(train_t_df) > self.max_training_sample: train_t_df = train_t_df.sample( @@ -303,7 +304,7 @@ def _train_and_score(self, df_decoy = df[df.decoy != 0] if ( - np.sum(df_target.fdr<=self.fdr) < + np.sum(df_target.fdr<=self.training_fdr) < self.min_training_sample or len(df_decoy) < self.min_training_sample ): @@ -347,7 +348,7 @@ def _cv_score(self, df:pd.DataFrame)->pd.DataFrame: df_decoy = df[df.decoy != 0] if ( - np.sum(df_target.fdrpd.DataFrame:\n", + " \"\"\"\n", + " This method allow us to update adaptive features\n", + " during the iteration of Percolator algorithm\n", + "\n", + " Parameters\n", + " ----------\n", + " psm_df : pd.DataFrame\n", + " psm_df\n", + " \n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " psm_df with updated feature values\n", + " \"\"\"\n", " return psm_df\n" ] }, @@ -149,6 +166,57 @@ "source": [ "show_doc(BaseFeatureExtractor.extract_features)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/feature_extraction_base.py#L52){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### BaseFeatureExtractor.update_features\n", + "\n", + "> BaseFeatureExtractor.update_features (psm_df:pandas.core.frame.DataFrame)\n", + "\n", + "This method allow us to update adaptive features\n", + "during the iteration of Percolator algorithm\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| psm_df | DataFrame | psm_df |\n", + "| **Returns** | **DataFrame** | **psm_df with updated feature values** |" + ], + "text/plain": [ + "---\n", + "\n", + "[source](https://github.com/MannLabs/alphabase/blob/main/alphabase/scoring/feature_extraction_base.py#L52){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "\n", + "### BaseFeatureExtractor.update_features\n", + "\n", + "> BaseFeatureExtractor.update_features (psm_df:pandas.core.frame.DataFrame)\n", + "\n", + "This method allow us to update adaptive features\n", + "during the iteration of Percolator algorithm\n", + "\n", + "| | **Type** | **Details** |\n", + "| -- | -------- | ----------- |\n", + "| psm_df | DataFrame | psm_df |\n", + "| **Returns** | **DataFrame** | **psm_df with updated feature values** |" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "show_doc(BaseFeatureExtractor.update_features)" + ] } ], "metadata": { diff --git a/nbdev_nbs/scoring/ml_scoring_base.ipynb b/nbdev_nbs/scoring/ml_scoring_base.ipynb index 080d44c4..439db435 100644 --- a/nbdev_nbs/scoring/ml_scoring_base.ipynb +++ b/nbdev_nbs/scoring/ml_scoring_base.ipynb @@ -105,12 +105,9 @@ "\n", "```python\n", "class DiaNNRescoring(Percolator):\n", - " def _train(self, train_t_df, train_d_df):\n", - " # No target filtration on FDR, which is the same as DiaNN but different from Percolator\n", - " #train_t_df = train_t_df[train_t_df.fdr<=self.fdr]\n", - " train_df = pd.concat((train_t_df, train_d_df))\n", - " train_label = np.ones(len(train_df),dtype=np.int32)\n", - " train_label[len(train_t_df):] = 0\n", + " def __init__(self):\n", + " super().__init__()\n", + " self.training_fdr = 100000 # disable target filtration on FDR, which is the same as DiaNN but different from Percolator\n", "\n", " self._ml_model.fit(\n", " train_df[self.feature_list].values, \n", @@ -139,7 +136,7 @@ " self._ml_model = LogisticRegression()\n", " \n", " self.fdr_level = 'psm' # psm, precursor, peptide, or sequence\n", - " self.fdr = 0.01\n", + " self.training_fdr = 0.01\n", " self.per_raw_fdr = False\n", "\n", " self.max_training_sample = 200000\n", @@ -227,6 +224,7 @@ " for i in range(self.iter_num):\n", " df = self._cv_score(df)\n", " df = self._estimate_fdr(df, 'psm', False)\n", + " df = self.feature_extractor.update_features(df)\n", " df = self._estimate_fdr(df)\n", " return df\n", "\n", @@ -376,7 +374,7 @@ " train_t_df:pd.DataFrame, \n", " train_d_df:pd.DataFrame\n", " ):\n", - " train_t_df = train_t_df[train_t_df.fdr<=self.fdr]\n", + " train_t_df = train_t_df[train_t_df.fdr<=self.training_fdr]\n", "\n", " if len(train_t_df) > self.max_training_sample:\n", " train_t_df = train_t_df.sample(\n", @@ -417,7 +415,7 @@ " df_decoy = df[df.decoy != 0]\n", "\n", " if (\n", - " np.sum(df_target.fdr<=self.fdr) < \n", + " np.sum(df_target.fdr<=self.training_fdr) < \n", " self.min_training_sample or\n", " len(df_decoy) < self.min_training_sample\n", " ):\n", @@ -461,7 +459,7 @@ " df_decoy = df[df.decoy != 0]\n", "\n", " if (\n", - " np.sum(df_target.fdr Date: Wed, 12 Oct 2022 20:31:56 +0200 Subject: [PATCH 11/52] add min_precursor_num_to_run_mp --- alphabase/peptide/precursor.py | 6 ++++++ alphabase/spectral_library/library_base.py | 4 ++-- nbdev_nbs/peptide/precursor.ipynb | 6 ++++++ nbdev_nbs/spectral_library/library_base.ipynb | 4 ++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/alphabase/peptide/precursor.py b/alphabase/peptide/precursor.py index 15d20a6e..7b5a88aa 100644 --- a/alphabase/peptide/precursor.py +++ b/alphabase/peptide/precursor.py @@ -490,6 +490,7 @@ def calc_precursor_isotope_mp( mp_batch_size:int=100000, process_bar=None, min_right_most_intensity:float=0.2, + min_precursor_num_to_run_mp:int=1000, )->pd.DataFrame: """`calc_precursor_isotope` is not that fast for large dataframes, so here we use multiprocessing for faster isotope pattern calculation. @@ -527,6 +528,11 @@ def calc_precursor_isotope_mp( - isotope_right_most_mz - isotope_right_most_index """ + if len(precursor_df) < min_precursor_num_to_run_mp: + return calc_precursor_isotope( + precursor_df=precursor_df, + min_right_most_intensity=min_right_most_intensity, + ) df_list = [] df_group = precursor_df.groupby('nAA') with mp.Pool(processes) as p: diff --git a/alphabase/spectral_library/library_base.py b/alphabase/spectral_library/library_base.py index 5858bd87..0e48b375 100644 --- a/alphabase/spectral_library/library_base.py +++ b/alphabase/spectral_library/library_base.py @@ -234,7 +234,7 @@ def calc_precursor_isotope(self, multiprocessing:bool=True, mp_process_num:int=8, mp_process_bar=None, - min_num_for_mp:int=1000, + min_precursor_num_to_run_mp:int=1000, ): """ Append isotope columns into self.precursor_df. @@ -243,7 +243,7 @@ def calc_precursor_isotope(self, if 'precursor_mz' not in self._precursor_df.columns: self.calc_precursor_mz() self.clip_by_precursor_mz_() - if multiprocessing and len(self.precursor_df)>min_num_for_mp: + if multiprocessing and len(self.precursor_df)>min_precursor_num_to_run_mp: ( self._precursor_df ) = precursor.calc_precursor_isotope_mp( diff --git a/nbdev_nbs/peptide/precursor.ipynb b/nbdev_nbs/peptide/precursor.ipynb index 961a0106..59436bce 100644 --- a/nbdev_nbs/peptide/precursor.ipynb +++ b/nbdev_nbs/peptide/precursor.ipynb @@ -529,6 +529,7 @@ " mp_batch_size:int=100000,\n", " process_bar=None,\n", " min_right_most_intensity:float=0.2,\n", + " min_precursor_num_to_run_mp:int=1000,\n", ")->pd.DataFrame:\n", " \"\"\"`calc_precursor_isotope` is not that fast for large dataframes, \n", " so here we use multiprocessing for faster isotope pattern calculation. \n", @@ -566,6 +567,11 @@ " - isotope_right_most_mz\n", " - isotope_right_most_index\n", " \"\"\"\n", + " if len(precursor_df) < min_precursor_num_to_run_mp:\n", + " return calc_precursor_isotope(\n", + " precursor_df=precursor_df,\n", + " min_right_most_intensity=min_right_most_intensity,\n", + " )\n", " df_list = []\n", " df_group = precursor_df.groupby('nAA')\n", " with mp.Pool(processes) as p:\n", diff --git a/nbdev_nbs/spectral_library/library_base.ipynb b/nbdev_nbs/spectral_library/library_base.ipynb index 93d4986f..c8bcffc7 100644 --- a/nbdev_nbs/spectral_library/library_base.ipynb +++ b/nbdev_nbs/spectral_library/library_base.ipynb @@ -261,7 +261,7 @@ " multiprocessing:bool=True,\n", " mp_process_num:int=8,\n", " mp_process_bar=None,\n", - " min_num_for_mp:int=1000,\n", + " min_precursor_num_to_run_mp:int=1000,\n", " ):\n", " \"\"\"\n", " Append isotope columns into self.precursor_df.\n", @@ -270,7 +270,7 @@ " if 'precursor_mz' not in self._precursor_df.columns:\n", " self.calc_precursor_mz()\n", " self.clip_by_precursor_mz_()\n", - " if multiprocessing and len(self.precursor_df)>min_num_for_mp:\n", + " if multiprocessing and len(self.precursor_df)>min_precursor_num_to_run_mp:\n", " (\n", " self._precursor_df\n", " ) = precursor.calc_precursor_isotope_mp(\n", From 93aa2fad2d367b61d356164b4582a9882eecacdc Mon Sep 17 00:00:00 2001 From: "Zeng, Wen-Feng" Date: Thu, 13 Oct 2022 10:18:07 +0200 Subject: [PATCH 12/52] move translate.py from AlphaPeptDeep --- alphabase/_modidx.py | 26 + alphabase/spectral_library/translate.py | 440 +++++++ nbdev_nbs/spectral_library/translate.ipynb | 1321 ++++++++++++++++++++ 3 files changed, 1787 insertions(+) create mode 100644 alphabase/spectral_library/translate.py create mode 100644 nbdev_nbs/spectral_library/translate.ipynb diff --git a/alphabase/_modidx.py b/alphabase/_modidx.py index 66b0f76b..4eb6b3da 100644 --- a/alphabase/_modidx.py +++ b/alphabase/_modidx.py @@ -524,6 +524,32 @@ 'alphabase/spectral_library/library_base.py'), 'alphabase.spectral_library.library_base.SpecLibBase.update_precursor_mz': ( 'spectral_library/library_base.html#speclibbase.update_precursor_mz', 'alphabase/spectral_library/library_base.py')}, + 'alphabase.spectral_library.translate': { 'alphabase.spectral_library.translate.WritingProcess': ( 'spectral_library/translate.html#writingprocess', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate.WritingProcess.__init__': ( 'spectral_library/translate.html#writingprocess.__init__', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate.WritingProcess.run': ( 'spectral_library/translate.html#writingprocess.run', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate._get_frag_info_from_column_name': ( 'spectral_library/translate.html#_get_frag_info_from_column_name', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate._get_frag_num': ( 'spectral_library/translate.html#_get_frag_num', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate.create_modified_sequence': ( 'spectral_library/translate.html#create_modified_sequence', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate.is_nterm_frag': ( 'spectral_library/translate.html#is_nterm_frag', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate.mask_fragment_intensity_by_frag_nAA': ( 'spectral_library/translate.html#mask_fragment_intensity_by_frag_naa', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate.mask_fragment_intensity_by_mz_': ( 'spectral_library/translate.html#mask_fragment_intensity_by_mz_', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate.merge_precursor_fragment_df': ( 'spectral_library/translate.html#merge_precursor_fragment_df', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate.speclib_to_single_df': ( 'spectral_library/translate.html#speclib_to_single_df', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate.speclib_to_swath_df': ( 'spectral_library/translate.html#speclib_to_swath_df', + 'alphabase/spectral_library/translate.py'), + 'alphabase.spectral_library.translate.translate_to_tsv': ( 'spectral_library/translate.html#translate_to_tsv', + 'alphabase/spectral_library/translate.py')}, 'alphabase.statistics.regression': { 'alphabase.statistics.regression.LOESSRegression': ( 'statistics/regression.html#loessregression', 'alphabase/statistics/regression.py'), 'alphabase.statistics.regression.LOESSRegression.__init__': ( 'statistics/regression.html#loessregression.__init__', diff --git a/alphabase/spectral_library/translate.py b/alphabase/spectral_library/translate.py new file mode 100644 index 00000000..55f0f280 --- /dev/null +++ b/alphabase/spectral_library/translate.py @@ -0,0 +1,440 @@ +# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbdev_nbs/spectral_library/translate.ipynb. + +# %% auto 0 +__all__ = ['mod_to_unimod_dict', 'mod_to_modname_dict', 'create_modified_sequence', 'merge_precursor_fragment_df', + 'is_nterm_frag', 'mask_fragment_intensity_by_mz_', 'mask_fragment_intensity_by_frag_nAA', + 'speclib_to_single_df', 'speclib_to_swath_df', 'WritingProcess', 'translate_to_tsv'] + +# %% ../../nbdev_nbs/spectral_library/translate.ipynb 3 +import pandas as pd +import numpy as np +import tqdm +import typing +import numba +import multiprocessing as mp + +from ..constants.modification import MOD_DF + +from .library_base import SpecLibBase + +from ..utils import explode_multiple_columns + +# %% ../../nbdev_nbs/spectral_library/translate.ipynb 4 +#@numba.njit #(cannot use numba for pd.Series) +def create_modified_sequence( + df_items:typing.Tuple, # must be ('sequence','mods','mod_sites') + translate_mod_dict:dict=None, + mod_sep='()', + nterm = '_', + cterm = '_' +): + ''' + Translate `(sequence, mods, mod_sites)` into a modified sequence. Used by `df.apply()`. + For example, `('ABCDEFG','Mod1@A;Mod2@E','1;5')`->`_A[Mod1@A]BCDE[Mod2@E]FG_`. + + Parameters + ---------- + df_items : List + must be `(sequence, mods, mod_sites)` + + translate_mod_dict : dict + A dict to map alpha modification names to other software + + mod_sep : str + '[]' or '()', default '()' + + ''' + mod_seq = df_items[0] + if df_items[1]: + mods = df_items[1].split(';') + mod_sites = [int(i) for i in df_items[2].split(';')] + rev_order = np.argsort(mod_sites)[::-1] + mod_sites = [mod_sites[rev_order[i]] for i in range(len(mod_sites))] + mods = [mods[rev_order[i]] for i in range(len(mods))] + if translate_mod_dict is not None: + mods = [translate_mod_dict[mod] for mod in mods] + for _site, mod in zip(mod_sites, mods): + if _site > 0: + mod_seq = mod_seq[:_site] + mod_sep[0]+mod+mod_sep[1] + mod_seq[_site:] + elif _site == -1: + cterm += mod_sep[0]+mod+mod_sep[1] + elif _site == 0: + nterm += mod_sep[0]+mod+mod_sep[1] + else: + mod_seq = mod_seq[:_site] + mod_sep[0]+mod+mod_sep[1] + mod_seq[_site:] + return nterm + mod_seq + cterm + +# %% ../../nbdev_nbs/spectral_library/translate.ipynb 9 +@numba.njit +def _get_frag_info_from_column_name(column:str): + ''' + Only used when converting alphabase libraries into other libraries + ''' + idx = column.rfind('_') + frag_type = column[:idx] + charge = column[idx+2:] + if len(frag_type)==1: + loss_type = 'noloss' + else: + idx = frag_type.find('_') + loss_type = frag_type[idx+1:] + frag_type = frag_type[0] + return frag_type, loss_type, charge + +def _get_frag_num(columns, rows, frag_len): + frag_nums = [] + for r,c in zip(rows, columns): + if is_nterm_frag(c): + frag_nums.append(r+1) + else: + frag_nums.append(frag_len-r) + return frag_nums + +def merge_precursor_fragment_df( + precursor_df:pd.DataFrame, + fragment_mz_df:pd.DataFrame, + fragment_inten_df:pd.DataFrame, + top_n_inten:int, + frag_type_head:str='FragmentType', + frag_mass_head:str='FragmentMz', + frag_inten_head:str='RelativeIntensity', + frag_charge_head:str='FragmentCharge', + frag_loss_head:str='FragmentLossType', + frag_num_head:str='FragmentNumber', + verbose=True, +): + ''' + Convert alphabase library into a single dataframe. + This method is not important, as it will be only + used by DiaNN, or spectronaut, or others + ''' + df = precursor_df.copy() + frag_columns = fragment_mz_df.columns.values.astype('U') + frag_type_list = [] + frag_loss_list = [] + frag_charge_list = [] + frag_mass_list = [] + frag_inten_list = [] + frag_num_list = [] + iters = enumerate(df[['frag_start_idx','frag_end_idx']].values) + if verbose: + iters = tqdm.tqdm(iters) + for i,(start, end) in iters: + intens = fragment_inten_df.iloc[start:end,:].values # is loc[start:end-1,:] faster? + max_inten = np.amax(intens) + if max_inten > 0: + intens /= max_inten + masses = fragment_mz_df.iloc[start:end,:].values + sorted_idx = np.argsort(intens.reshape(-1))[-top_n_inten:][::-1] + idx_in_df = np.unravel_index(sorted_idx, masses.shape) + + frag_len = end-start + rows = np.arange(frag_len, dtype=np.int32)[idx_in_df[0]] + columns = frag_columns[idx_in_df[1]] + + frag_types, loss_types, charges = zip( + *[_get_frag_info_from_column_name(_) for _ in columns] + ) + + frag_nums = _get_frag_num(columns, rows, frag_len) + + frag_type_list.append(frag_types) + frag_loss_list.append(loss_types) + frag_charge_list.append(charges) + frag_mass_list.append(masses[idx_in_df]) + frag_inten_list.append(intens[idx_in_df]) + frag_num_list.append(frag_nums) + + df[frag_type_head] = frag_type_list + df[frag_mass_head] = frag_mass_list + df[frag_inten_head] = frag_inten_list + df[frag_charge_head] = frag_charge_list + df[frag_loss_head] = frag_loss_list + df[frag_num_head] = frag_num_list + + return explode_multiple_columns(df, + [ + frag_type_head, + frag_mass_head, + frag_inten_head, + frag_charge_head, + frag_loss_head, + frag_num_head + ] + ) + + # try: + # return df.explode([ + # frag_type_head, + # frag_mass_head, + # frag_inten_head, + # frag_charge_head, + # frag_loss_head, + # frag_num_head + # ]) + # except ValueError: + # # df.explode does not allow mulitple columns before pandas version 1.x.x. + # df = df.explode(frag_type_head) + + # df[frag_mass_head] = _flatten(frag_mass_list) + # df[frag_inten_head] = _flatten(frag_inten_list) + # df[frag_charge_head] = _flatten(frag_charge_list) + # df[frag_loss_head] = _flatten(frag_loss_list) + # df[frag_num_head] = _flatten(frag_num_list) + # return df + +mod_to_unimod_dict = {} +mod_to_modname_dict = {} +for mod_name,unimod_id in MOD_DF[['mod_name','unimod_id']].values: + if unimod_id==-1 or unimod_id=='-1': continue + mod_to_unimod_dict[mod_name] = f"UniMod:{unimod_id}" + mod_to_modname_dict[mod_name] = mod_name[:mod_name.find('@')] + +def is_nterm_frag(frag_type:str): + return frag_type[0] in 'abc' + +def mask_fragment_intensity_by_mz_( + fragment_mz_df:pd.DataFrame, + fragment_intensity_df:pd.DataFrame, + min_frag_mz, max_frag_mz +): + fragment_intensity_df.mask( + (fragment_mz_df>max_frag_mz)|(fragment_mz_dfpd.DataFrame: + ''' + Convert alphabase library to diann (or Spectronaut) library dataframe + This method is not important, as it will be only + used by DiaNN, or spectronaut, or others + + Parameters + ---------- + translate_mod_dict : dict + a dict map modifications from alphabase to other software. Default: build-in `alpha_to_other_mod_dict` + + keep_k_highest_peaks : int + only keep highest fragments for each precursor. Default: 12 + + Returns + ------- + pd.DataFrame + a single dataframe in the SWATH-like format + + ''' + df = pd.DataFrame() + df['ModifiedPeptide'] = speclib._precursor_df[ + ['sequence','mods','mod_sites'] + ].apply( + create_modified_sequence, + axis=1, + translate_mod_dict=translate_mod_dict, + mod_sep='()' + ) + + df['frag_start_idx'] = speclib._precursor_df['frag_start_idx'] + df['frag_end_idx'] = speclib._precursor_df['frag_end_idx'] + + df['PrecursorCharge'] = speclib._precursor_df['charge'] + if 'irt_pred' in speclib._precursor_df.columns: + df['Tr_recalibrated'] = speclib._precursor_df['irt_pred'] + elif 'rt_pred' in speclib._precursor_df.columns: + df['Tr_recalibrated'] = speclib._precursor_df['rt_pred'] + elif 'rt_norm' in speclib._precursor_df.columns: + df['Tr_recalibrated'] = speclib._precursor_df['rt_norm'] + else: + raise ValueError('precursor_df must contain the "rt_pred" or "rt_norm" column') + + if 'mobility_pred' in speclib._precursor_df.columns: + df['IonMobility'] = speclib._precursor_df.mobility_pred + elif 'mobility' in speclib._precursor_df.columns: + df['IonMobility'] = speclib._precursor_df.mobility + + # df['LabelModifiedSequence'] = df['ModifiedPeptide'] + df['StrippedPeptide'] = speclib._precursor_df['sequence'] + + if 'precursor_mz' not in speclib._precursor_df.columns: + speclib.calc_precursor_mz() + df['PrecursorMz'] = speclib._precursor_df['precursor_mz'] + + if 'uniprot_ids' in speclib._precursor_df.columns: + df['ProteinID'] = speclib._precursor_df.uniprot_ids + elif 'proteins' in speclib._precursor_df.columns: + df['ProteinID'] = speclib._precursor_df.proteins + + if 'genes' in speclib._precursor_df.columns: + df['Genes'] = speclib._precursor_df['genes'] + + # if 'protein_group' in speclib._precursor_df.columns: + # df['ProteinGroups'] = speclib._precursor_df['protein_group'] + + if min_frag_mz > 0 or max_frag_mz > 0: + mask_fragment_intensity_by_mz_( + speclib._fragment_mz_df, + speclib._fragment_intensity_df, + min_frag_mz, max_frag_mz + ) + + if min_frag_nAA > 0: + mask_fragment_intensity_by_frag_nAA( + speclib._fragment_intensity_df, + speclib._precursor_df, + max_mask_frag_nAA=min_frag_nAA-1 + ) + + df = merge_precursor_fragment_df( + df, + speclib._fragment_mz_df, + speclib._fragment_intensity_df, + top_n_inten=keep_k_highest_fragments, + frag_type_head=frag_type_head, + frag_mass_head=frag_mass_head, + frag_inten_head=frag_inten_head, + frag_charge_head=frag_charge_head, + frag_loss_head=frag_loss_head, + frag_num_head=frag_num_head, + verbose=verbose + ) + df = df[df['RelativeIntensity']>min_frag_intensity] + df.loc[df[frag_loss_head]=='modloss',frag_loss_head] = modloss + + return df.drop(['frag_start_idx','frag_end_idx'], axis=1) + +def speclib_to_swath_df( + speclib:SpecLibBase, + *, + keep_k_highest_fragments:int=12, + min_frag_mz = 200, + max_frag_mz = 2000, + min_frag_intensity = 0.01, +)->pd.DataFrame: + speclib_to_single_df( + speclib, + translate_mod_dict=mod_to_modname_dict, + keep_k_highest_fragments=keep_k_highest_fragments, + min_frag_mz = min_frag_mz, + max_frag_mz = max_frag_mz, + min_frag_intensity = min_frag_intensity, + ) + +class WritingProcess(mp.Process): + def __init__(self, task_queue, tsv, *args, **kwargs): + self.task_queue:mp.Queue = task_queue + self.tsv = tsv + super().__init__(*args, **kwargs) + + def run(self): + while True: + df, batch = self.task_queue.get() + if df is None: break + df.to_csv(self.tsv, header=(batch==0), sep="\t", mode="a", index=False) + + +def translate_to_tsv( + speclib:SpecLibBase, + tsv:str, + *, + keep_k_highest_fragments:int=12, + min_frag_mz:float = 200, + max_frag_mz:float = 2000, + min_frag_intensity:float = 0.01, + min_frag_nAA:int = 0, + batch_size:int = 100000, + translate_mod_dict:dict = mod_to_modname_dict, + multiprocessing:bool=True +): + if multiprocessing: + queue_size = 1000000//batch_size + if queue_size < 2: + queue_size = 2 + elif queue_size > 10: + queue_size = 10 + df_head_queue = mp.Queue(maxsize=queue_size) + writing_process = WritingProcess(df_head_queue, tsv) + writing_process.start() + mask_fragment_intensity_by_mz_( + speclib._fragment_mz_df, + speclib._fragment_intensity_df, + min_frag_mz, max_frag_mz + ) + if min_frag_nAA > 0: + mask_fragment_intensity_by_frag_nAA( + speclib._fragment_intensity_df, + speclib._precursor_df, + max_mask_frag_nAA=min_frag_nAA-1 + ) + if isinstance(tsv, str): + with open(tsv, "w"): pass + _speclib = SpecLibBase() + _speclib._fragment_intensity_df = speclib._fragment_intensity_df + _speclib._fragment_mz_df = speclib._fragment_mz_df + precursor_df = speclib._precursor_df + for i in tqdm.tqdm(range(0, len(precursor_df), batch_size)): + _speclib._precursor_df = precursor_df.iloc[i:i+batch_size] + df = speclib_to_single_df( + _speclib, translate_mod_dict=translate_mod_dict, + keep_k_highest_fragments=keep_k_highest_fragments, + min_frag_mz=0, + max_frag_mz=0, + min_frag_intensity=min_frag_intensity, + min_frag_nAA=0, + verbose=False + ) + if multiprocessing: + df_head_queue.put((df, i)) + else: + df.to_csv(tsv, header=(i==0), sep="\t", mode='a', index=False) + if multiprocessing: + df_head_queue.put((None, None)) + print("Translation finished, it will take several minutes to export the rest precursors to the tsv file...") + writing_process.join() + diff --git a/nbdev_nbs/spectral_library/translate.ipynb b/nbdev_nbs/spectral_library/translate.ipynb new file mode 100644 index 00000000..a697e59a --- /dev/null +++ b/nbdev_nbs/spectral_library/translate.ipynb @@ -0,0 +1,1321 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| default_exp spectral_library.translate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Translate Spectral Libraries" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Translate peptdeep spectral libraries into other formats (e.g. TSV)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "import pandas as pd\n", + "import numpy as np\n", + "import tqdm\n", + "import typing\n", + "import numba\n", + "import multiprocessing as mp\n", + "\n", + "from alphabase.constants.modification import MOD_DF\n", + "\n", + "from alphabase.spectral_library.library_base import SpecLibBase\n", + "\n", + "from alphabase.utils import explode_multiple_columns" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "#@numba.njit #(cannot use numba for pd.Series)\n", + "def create_modified_sequence(\n", + " df_items:typing.Tuple, # must be ('sequence','mods','mod_sites')\n", + " translate_mod_dict:dict=None,\n", + " mod_sep='()',\n", + " nterm = '_',\n", + " cterm = '_'\n", + "):\n", + " '''\n", + " Translate `(sequence, mods, mod_sites)` into a modified sequence. Used by `df.apply()`.\n", + " For example, `('ABCDEFG','Mod1@A;Mod2@E','1;5')`->`_A[Mod1@A]BCDE[Mod2@E]FG_`.\n", + "\n", + " Parameters\n", + " ----------\n", + " df_items : List\n", + " must be `(sequence, mods, mod_sites)`\n", + "\n", + " translate_mod_dict : dict\n", + " A dict to map alpha modification names to other software\n", + "\n", + " mod_sep : str\n", + " '[]' or '()', default '()'\n", + "\n", + " '''\n", + " mod_seq = df_items[0]\n", + " if df_items[1]:\n", + " mods = df_items[1].split(';')\n", + " mod_sites = [int(i) for i in df_items[2].split(';')]\n", + " rev_order = np.argsort(mod_sites)[::-1]\n", + " mod_sites = [mod_sites[rev_order[i]] for i in range(len(mod_sites))]\n", + " mods = [mods[rev_order[i]] for i in range(len(mods))]\n", + " if translate_mod_dict is not None:\n", + " mods = [translate_mod_dict[mod] for mod in mods]\n", + " for _site, mod in zip(mod_sites, mods):\n", + " if _site > 0:\n", + " mod_seq = mod_seq[:_site] + mod_sep[0]+mod+mod_sep[1] + mod_seq[_site:]\n", + " elif _site == -1:\n", + " cterm += mod_sep[0]+mod+mod_sep[1]\n", + " elif _site == 0:\n", + " nterm += mod_sep[0]+mod+mod_sep[1]\n", + " else:\n", + " mod_seq = mod_seq[:_site] + mod_sep[0]+mod+mod_sep[1] + mod_seq[_site:]\n", + " return nterm + mod_seq + cterm" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0 _(ModNterm)AC(ModA@C)DEFG(ModB@G)HIK_(ModCterm)\n", + "1 _(ModNterm)AC(ModA@C)DEFG(ModB@G)HIK_(ModCterm)\n", + "2 _(ModNterm)AC(ModA@C)DEFG(ModB@G)HIK_(ModCterm)\n", + "3 _(ModNterm)AC(ModA@C)DEFG(ModB@G)HIK_(ModCterm)\n", + "4 _(ModNterm)AC(ModA@C)DEFG(ModB@G)HIK_(ModCterm)\n", + "5 _(ModNterm)AC(ModA@C)DEFG(ModB@G)HIK_(ModCterm)\n", + "6 _(ModNterm)AC(ModA@C)DEFG(ModB@G)HIK_(ModCterm)\n", + "7 _(ModNterm)AC(ModA@C)DEFG(ModB@G)HIK_(ModCterm)\n", + "8 _(ModNterm)AC(ModA@C)DEFG(ModB@G)HIK_(ModCterm)\n", + "9 _(ModNterm)AC(ModA@C)DEFG(ModB@G)HIK_(ModCterm)\n", + "dtype: object" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = pd.DataFrame()\n", + "df['sequence'] = ['ACDEFGHIK']*10\n", + "df['mods'] = ['ModNterm;ModB@G;ModCterm;ModA@C']*10\n", + "df['mod_sites'] = ['0;6;-1;2']*10\n", + "df[['sequence','mods','mod_sites']].apply(create_modified_sequence, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert create_modified_sequence(('ACDEFGHIK','ModNterm;ModB@G;ModCterm;ModA@C','0;6;-1;2'), mod_sep='[]')=='_[ModNterm]AC[ModA@C]DEFG[ModB@G]HIK_[ModCterm]'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert create_modified_sequence(\n", + " ('ACDEFGHIK','ModNterm;ModB@G;ModCterm;ModA@C','0;6;-1;2'),\n", + " {'ModNterm':'Mod(Nterm)', 'ModCterm':'Mod(Cterm)', 'ModA@C':'ModA(C)', 'ModB@G':'ModB(G)'},\n", + " mod_sep='()'\n", + ") == '_(Mod(Nterm))AC(ModA(C))DEFG(ModB(G))HIK_(Mod(Cterm))'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert create_modified_sequence(\n", + " ('ACDEFGHIK','ModNterm;ModB@G;ModCterm;ModA@C','0;6;-1;2'),\n", + " {'ModNterm':'Mod(Nterm)', 'ModCterm':'Mod(Cterm)', 'ModA@C':'ModA(C)', 'ModB@G':'ModB(G)'},\n", + " mod_sep='()', nterm='', cterm=''\n", + ") == '(Mod(Nterm))AC(ModA(C))DEFG(ModB(G))HIK(Mod(Cterm))'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "\n", + "@numba.njit\n", + "def _get_frag_info_from_column_name(column:str):\n", + " '''\n", + " Only used when converting alphabase libraries into other libraries\n", + " '''\n", + " idx = column.rfind('_')\n", + " frag_type = column[:idx]\n", + " charge = column[idx+2:]\n", + " if len(frag_type)==1:\n", + " loss_type = 'noloss'\n", + " else:\n", + " idx = frag_type.find('_')\n", + " loss_type = frag_type[idx+1:]\n", + " frag_type = frag_type[0]\n", + " return frag_type, loss_type, charge\n", + "\n", + "def _get_frag_num(columns, rows, frag_len):\n", + " frag_nums = []\n", + " for r,c in zip(rows, columns):\n", + " if is_nterm_frag(c):\n", + " frag_nums.append(r+1)\n", + " else:\n", + " frag_nums.append(frag_len-r)\n", + " return frag_nums\n", + "\n", + "def merge_precursor_fragment_df(\n", + " precursor_df:pd.DataFrame, \n", + " fragment_mz_df:pd.DataFrame, \n", + " fragment_inten_df:pd.DataFrame, \n", + " top_n_inten:int,\n", + " frag_type_head:str='FragmentType',\n", + " frag_mass_head:str='FragmentMz',\n", + " frag_inten_head:str='RelativeIntensity',\n", + " frag_charge_head:str='FragmentCharge',\n", + " frag_loss_head:str='FragmentLossType',\n", + " frag_num_head:str='FragmentNumber',\n", + " verbose=True,\n", + "):\n", + " '''\n", + " Convert alphabase library into a single dataframe. \n", + " This method is not important, as it will be only \n", + " used by DiaNN, or spectronaut, or others\n", + " '''\n", + " df = precursor_df.copy()\n", + " frag_columns = fragment_mz_df.columns.values.astype('U')\n", + " frag_type_list = []\n", + " frag_loss_list = []\n", + " frag_charge_list = []\n", + " frag_mass_list = []\n", + " frag_inten_list = []\n", + " frag_num_list = []\n", + " iters = enumerate(df[['frag_start_idx','frag_end_idx']].values)\n", + " if verbose:\n", + " iters = tqdm.tqdm(iters)\n", + " for i,(start, end) in iters:\n", + " intens = fragment_inten_df.iloc[start:end,:].values # is loc[start:end-1,:] faster?\n", + " max_inten = np.amax(intens)\n", + " if max_inten > 0:\n", + " intens /= max_inten\n", + " masses = fragment_mz_df.iloc[start:end,:].values\n", + " sorted_idx = np.argsort(intens.reshape(-1))[-top_n_inten:][::-1]\n", + " idx_in_df = np.unravel_index(sorted_idx, masses.shape)\n", + "\n", + " frag_len = end-start\n", + " rows = np.arange(frag_len, dtype=np.int32)[idx_in_df[0]]\n", + " columns = frag_columns[idx_in_df[1]]\n", + "\n", + " frag_types, loss_types, charges = zip(\n", + " *[_get_frag_info_from_column_name(_) for _ in columns]\n", + " )\n", + "\n", + " frag_nums = _get_frag_num(columns, rows, frag_len)\n", + "\n", + " frag_type_list.append(frag_types)\n", + " frag_loss_list.append(loss_types)\n", + " frag_charge_list.append(charges)\n", + " frag_mass_list.append(masses[idx_in_df])\n", + " frag_inten_list.append(intens[idx_in_df])\n", + " frag_num_list.append(frag_nums)\n", + " \n", + " df[frag_type_head] = frag_type_list\n", + " df[frag_mass_head] = frag_mass_list\n", + " df[frag_inten_head] = frag_inten_list\n", + " df[frag_charge_head] = frag_charge_list\n", + " df[frag_loss_head] = frag_loss_list\n", + " df[frag_num_head] = frag_num_list\n", + "\n", + " return explode_multiple_columns(df, \n", + " [\n", + " frag_type_head,\n", + " frag_mass_head,\n", + " frag_inten_head,\n", + " frag_charge_head,\n", + " frag_loss_head,\n", + " frag_num_head\n", + " ]\n", + " )\n", + "\n", + " # try:\n", + " # return df.explode([\n", + " # frag_type_head,\n", + " # frag_mass_head,\n", + " # frag_inten_head,\n", + " # frag_charge_head,\n", + " # frag_loss_head,\n", + " # frag_num_head\n", + " # ])\n", + " # except ValueError:\n", + " # # df.explode does not allow mulitple columns before pandas version 1.x.x.\n", + " # df = df.explode(frag_type_head)\n", + "\n", + " # df[frag_mass_head] = _flatten(frag_mass_list)\n", + " # df[frag_inten_head] = _flatten(frag_inten_list)\n", + " # df[frag_charge_head] = _flatten(frag_charge_list)\n", + " # df[frag_loss_head] = _flatten(frag_loss_list)\n", + " # df[frag_num_head] = _flatten(frag_num_list)\n", + " # return df\n", + "\n", + "mod_to_unimod_dict = {}\n", + "mod_to_modname_dict = {}\n", + "for mod_name,unimod_id in MOD_DF[['mod_name','unimod_id']].values:\n", + " if unimod_id==-1 or unimod_id=='-1': continue\n", + " mod_to_unimod_dict[mod_name] = f\"UniMod:{unimod_id}\"\n", + " mod_to_modname_dict[mod_name] = mod_name[:mod_name.find('@')]\n", + "\n", + "def is_nterm_frag(frag_type:str):\n", + " return frag_type[0] in 'abc'\n", + "\n", + "def mask_fragment_intensity_by_mz_(\n", + " fragment_mz_df:pd.DataFrame, \n", + " fragment_intensity_df:pd.DataFrame,\n", + " min_frag_mz, max_frag_mz\n", + "):\n", + " fragment_intensity_df.mask(\n", + " (fragment_mz_df>max_frag_mz)|(fragment_mz_dfpd.DataFrame:\n", + " '''\n", + " Convert alphabase library to diann (or Spectronaut) library dataframe\n", + " This method is not important, as it will be only \n", + " used by DiaNN, or spectronaut, or others\n", + "\n", + " Parameters\n", + " ----------\n", + " translate_mod_dict : dict\n", + " a dict map modifications from alphabase to other software. Default: build-in `alpha_to_other_mod_dict`\n", + " \n", + " keep_k_highest_peaks : int\n", + " only keep highest fragments for each precursor. Default: 12\n", + "\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " a single dataframe in the SWATH-like format\n", + "\n", + " '''\n", + " df = pd.DataFrame()\n", + " df['ModifiedPeptide'] = speclib._precursor_df[\n", + " ['sequence','mods','mod_sites']\n", + " ].apply(\n", + " create_modified_sequence, \n", + " axis=1,\n", + " translate_mod_dict=translate_mod_dict,\n", + " mod_sep='()'\n", + " )\n", + "\n", + " df['frag_start_idx'] = speclib._precursor_df['frag_start_idx']\n", + " df['frag_end_idx'] = speclib._precursor_df['frag_end_idx']\n", + " \n", + " df['PrecursorCharge'] = speclib._precursor_df['charge']\n", + " if 'irt_pred' in speclib._precursor_df.columns:\n", + " df['Tr_recalibrated'] = speclib._precursor_df['irt_pred']\n", + " elif 'rt_pred' in speclib._precursor_df.columns:\n", + " df['Tr_recalibrated'] = speclib._precursor_df['rt_pred']\n", + " elif 'rt_norm' in speclib._precursor_df.columns:\n", + " df['Tr_recalibrated'] = speclib._precursor_df['rt_norm']\n", + " else:\n", + " raise ValueError('precursor_df must contain the \"rt_pred\" or \"rt_norm\" column')\n", + "\n", + " if 'mobility_pred' in speclib._precursor_df.columns:\n", + " df['IonMobility'] = speclib._precursor_df.mobility_pred\n", + " elif 'mobility' in speclib._precursor_df.columns:\n", + " df['IonMobility'] = speclib._precursor_df.mobility\n", + " \n", + " # df['LabelModifiedSequence'] = df['ModifiedPeptide']\n", + " df['StrippedPeptide'] = speclib._precursor_df['sequence']\n", + "\n", + " if 'precursor_mz' not in speclib._precursor_df.columns:\n", + " speclib.calc_precursor_mz()\n", + " df['PrecursorMz'] = speclib._precursor_df['precursor_mz']\n", + "\n", + " if 'uniprot_ids' in speclib._precursor_df.columns:\n", + " df['ProteinID'] = speclib._precursor_df.uniprot_ids\n", + " elif 'proteins' in speclib._precursor_df.columns:\n", + " df['ProteinID'] = speclib._precursor_df.proteins\n", + "\n", + " if 'genes' in speclib._precursor_df.columns:\n", + " df['Genes'] = speclib._precursor_df['genes']\n", + "\n", + " # if 'protein_group' in speclib._precursor_df.columns:\n", + " # df['ProteinGroups'] = speclib._precursor_df['protein_group']\n", + "\n", + " if min_frag_mz > 0 or max_frag_mz > 0:\n", + " mask_fragment_intensity_by_mz_(\n", + " speclib._fragment_mz_df,\n", + " speclib._fragment_intensity_df,\n", + " min_frag_mz, max_frag_mz\n", + " )\n", + "\n", + " if min_frag_nAA > 0:\n", + " mask_fragment_intensity_by_frag_nAA(\n", + " speclib._fragment_intensity_df,\n", + " speclib._precursor_df,\n", + " max_mask_frag_nAA=min_frag_nAA-1\n", + " )\n", + "\n", + " df = merge_precursor_fragment_df(\n", + " df,\n", + " speclib._fragment_mz_df,\n", + " speclib._fragment_intensity_df,\n", + " top_n_inten=keep_k_highest_fragments,\n", + " frag_type_head=frag_type_head,\n", + " frag_mass_head=frag_mass_head,\n", + " frag_inten_head=frag_inten_head,\n", + " frag_charge_head=frag_charge_head,\n", + " frag_loss_head=frag_loss_head,\n", + " frag_num_head=frag_num_head,\n", + " verbose=verbose\n", + " )\n", + " df = df[df['RelativeIntensity']>min_frag_intensity]\n", + " df.loc[df[frag_loss_head]=='modloss',frag_loss_head] = modloss\n", + "\n", + " return df.drop(['frag_start_idx','frag_end_idx'], axis=1)\n", + "\n", + "def speclib_to_swath_df(\n", + " speclib:SpecLibBase,\n", + " *,\n", + " keep_k_highest_fragments:int=12,\n", + " min_frag_mz = 200,\n", + " max_frag_mz = 2000,\n", + " min_frag_intensity = 0.01,\n", + ")->pd.DataFrame:\n", + " speclib_to_single_df(\n", + " speclib, \n", + " translate_mod_dict=mod_to_modname_dict,\n", + " keep_k_highest_fragments=keep_k_highest_fragments,\n", + " min_frag_mz = min_frag_mz,\n", + " max_frag_mz = max_frag_mz,\n", + " min_frag_intensity = min_frag_intensity,\n", + " )\n", + "\n", + "class WritingProcess(mp.Process):\n", + " def __init__(self, task_queue, tsv, *args, **kwargs):\n", + " self.task_queue:mp.Queue = task_queue\n", + " self.tsv = tsv\n", + " super().__init__(*args, **kwargs)\n", + "\n", + " def run(self):\n", + " while True:\n", + " df, batch = self.task_queue.get()\n", + " if df is None: break\n", + " df.to_csv(self.tsv, header=(batch==0), sep=\"\\t\", mode=\"a\", index=False)\n", + "\n", + "\n", + "def translate_to_tsv(\n", + " speclib:SpecLibBase,\n", + " tsv:str,\n", + " *,\n", + " keep_k_highest_fragments:int=12,\n", + " min_frag_mz:float = 200,\n", + " max_frag_mz:float = 2000,\n", + " min_frag_intensity:float = 0.01,\n", + " min_frag_nAA:int = 0,\n", + " batch_size:int = 100000,\n", + " translate_mod_dict:dict = mod_to_modname_dict,\n", + " multiprocessing:bool=True\n", + "):\n", + " if multiprocessing:\n", + " queue_size = 1000000//batch_size\n", + " if queue_size < 2:\n", + " queue_size = 2\n", + " elif queue_size > 10:\n", + " queue_size = 10\n", + " df_head_queue = mp.Queue(maxsize=queue_size)\n", + " writing_process = WritingProcess(df_head_queue, tsv)\n", + " writing_process.start()\n", + " mask_fragment_intensity_by_mz_(\n", + " speclib._fragment_mz_df,\n", + " speclib._fragment_intensity_df,\n", + " min_frag_mz, max_frag_mz\n", + " )\n", + " if min_frag_nAA > 0:\n", + " mask_fragment_intensity_by_frag_nAA(\n", + " speclib._fragment_intensity_df,\n", + " speclib._precursor_df,\n", + " max_mask_frag_nAA=min_frag_nAA-1\n", + " )\n", + " if isinstance(tsv, str):\n", + " with open(tsv, \"w\"): pass\n", + " _speclib = SpecLibBase()\n", + " _speclib._fragment_intensity_df = speclib._fragment_intensity_df\n", + " _speclib._fragment_mz_df = speclib._fragment_mz_df\n", + " precursor_df = speclib._precursor_df\n", + " for i in tqdm.tqdm(range(0, len(precursor_df), batch_size)):\n", + " _speclib._precursor_df = precursor_df.iloc[i:i+batch_size]\n", + " df = speclib_to_single_df(\n", + " _speclib, translate_mod_dict=translate_mod_dict,\n", + " keep_k_highest_fragments=keep_k_highest_fragments,\n", + " min_frag_mz=0,\n", + " max_frag_mz=0,\n", + " min_frag_intensity=min_frag_intensity,\n", + " min_frag_nAA=0,\n", + " verbose=False\n", + " )\n", + " if multiprocessing:\n", + " df_head_queue.put((df, i))\n", + " else:\n", + " df.to_csv(tsv, header=(i==0), sep=\"\\t\", mode='a', index=False)\n", + " if multiprocessing:\n", + " df_head_queue.put((None, None))\n", + " print(\"Translation finished, it will take several minutes to export the rest precursors to the tsv file...\")\n", + " writing_process.join()\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from alphabase.peptide.fragment import create_fragment_mz_dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
b_z1y_z1y_modloss_z1
072.0443901376.5275551278.550659
1239.0427501209.5291950.000000
2296.0642131152.5077320.000000
3433.1231251015.4488200.000000
4536.132310912.4396350.000000
............
105585.208572634.3129780.000000
106771.287885448.2336650.000000
107902.328370317.1931800.000000
108973.365484246.1560660.000000
1091044.402598175.1189520.000000
\n", + "

110 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " b_z1 y_z1 y_modloss_z1\n", + "0 72.044390 1376.527555 1278.550659\n", + "1 239.042750 1209.529195 0.000000\n", + "2 296.064213 1152.507732 0.000000\n", + "3 433.123125 1015.448820 0.000000\n", + "4 536.132310 912.439635 0.000000\n", + ".. ... ... ...\n", + "105 585.208572 634.312978 0.000000\n", + "106 771.287885 448.233665 0.000000\n", + "107 902.328370 317.193180 0.000000\n", + "108 973.365484 246.156066 0.000000\n", + "109 1044.402598 175.118952 0.000000\n", + "\n", + "[110 rows x 3 columns]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "repeat = 10\n", + "charged_frag_types = ['b_z1','y_z1','y_modloss_z1']\n", + "precursor_df = pd.DataFrame({\n", + " 'sequence': ['ASGHCEWMKYR']*repeat+['ASGHCEWMAAR'],\n", + " 'mods': ['Acetyl@Protein N-term;Carbamidomethyl@C;Oxidation@M']*repeat+[''],\n", + " 'mod_sites': ['0;4;8']*repeat+[''],\n", + " 'nAA': 11,\n", + " 'NCE': 20,\n", + " 'instrument': 'QE',\n", + " 'rt_pred': 10,\n", + " 'charge': 2,\n", + " 'protein_name': 'unknown',\n", + " 'mobility_pred': 1,\n", + "})\n", + "precursor_df.loc[0,['mods','mod_sites']] = ['Phospho@S','2']\n", + "frag_mass_df = create_fragment_mz_dataframe(precursor_df, charged_frag_types)\n", + "frag_mass_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sequencemodsmod_sitesnAANCEinstrumentrt_predchargeprotein_namemobility_predfrag_start_idxfrag_end_idx
0ASGHCEWMKYRPhospho@S21120QE102unknown1010
1ASGHCEWMKYRAcetyl@Protein N-term;Carbamidomethyl@C;Oxidat...0;4;81120QE102unknown11020
2ASGHCEWMKYRAcetyl@Protein N-term;Carbamidomethyl@C;Oxidat...0;4;81120QE102unknown12030
3ASGHCEWMKYRAcetyl@Protein N-term;Carbamidomethyl@C;Oxidat...0;4;81120QE102unknown13040
4ASGHCEWMKYRAcetyl@Protein N-term;Carbamidomethyl@C;Oxidat...0;4;81120QE102unknown14050
5ASGHCEWMKYRAcetyl@Protein N-term;Carbamidomethyl@C;Oxidat...0;4;81120QE102unknown15060
6ASGHCEWMKYRAcetyl@Protein N-term;Carbamidomethyl@C;Oxidat...0;4;81120QE102unknown16070
7ASGHCEWMKYRAcetyl@Protein N-term;Carbamidomethyl@C;Oxidat...0;4;81120QE102unknown17080
8ASGHCEWMKYRAcetyl@Protein N-term;Carbamidomethyl@C;Oxidat...0;4;81120QE102unknown18090
9ASGHCEWMKYRAcetyl@Protein N-term;Carbamidomethyl@C;Oxidat...0;4;81120QE102unknown190100
10ASGHCEWMAAR1120QE102unknown1100110
\n", + "
" + ], + "text/plain": [ + " sequence mods mod_sites \\\n", + "0 ASGHCEWMKYR Phospho@S 2 \n", + "1 ASGHCEWMKYR Acetyl@Protein N-term;Carbamidomethyl@C;Oxidat... 0;4;8 \n", + "2 ASGHCEWMKYR Acetyl@Protein N-term;Carbamidomethyl@C;Oxidat... 0;4;8 \n", + "3 ASGHCEWMKYR Acetyl@Protein N-term;Carbamidomethyl@C;Oxidat... 0;4;8 \n", + "4 ASGHCEWMKYR Acetyl@Protein N-term;Carbamidomethyl@C;Oxidat... 0;4;8 \n", + "5 ASGHCEWMKYR Acetyl@Protein N-term;Carbamidomethyl@C;Oxidat... 0;4;8 \n", + "6 ASGHCEWMKYR Acetyl@Protein N-term;Carbamidomethyl@C;Oxidat... 0;4;8 \n", + "7 ASGHCEWMKYR Acetyl@Protein N-term;Carbamidomethyl@C;Oxidat... 0;4;8 \n", + "8 ASGHCEWMKYR Acetyl@Protein N-term;Carbamidomethyl@C;Oxidat... 0;4;8 \n", + "9 ASGHCEWMKYR Acetyl@Protein N-term;Carbamidomethyl@C;Oxidat... 0;4;8 \n", + "10 ASGHCEWMAAR \n", + "\n", + " nAA NCE instrument rt_pred charge protein_name mobility_pred \\\n", + "0 11 20 QE 10 2 unknown 1 \n", + "1 11 20 QE 10 2 unknown 1 \n", + "2 11 20 QE 10 2 unknown 1 \n", + "3 11 20 QE 10 2 unknown 1 \n", + "4 11 20 QE 10 2 unknown 1 \n", + "5 11 20 QE 10 2 unknown 1 \n", + "6 11 20 QE 10 2 unknown 1 \n", + "7 11 20 QE 10 2 unknown 1 \n", + "8 11 20 QE 10 2 unknown 1 \n", + "9 11 20 QE 10 2 unknown 1 \n", + "10 11 20 QE 10 2 unknown 1 \n", + "\n", + " frag_start_idx frag_end_idx \n", + "0 0 10 \n", + "1 10 20 \n", + "2 20 30 \n", + "3 30 40 \n", + "4 40 50 \n", + "5 50 60 \n", + "6 60 70 \n", + "7 70 80 \n", + "8 80 90 \n", + "9 90 100 \n", + "10 100 110 " + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "precursor_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "11it [00:01, 6.85it/s]\n", + "11it [00:00, 2684.12it/s]\n" + ] + } + ], + "source": [ + "spec_lib = SpecLibBase(charged_frag_types)\n", + "spec_lib._precursor_df = precursor_df\n", + "spec_lib._fragment_intensity_df = frag_mass_df.copy()\n", + "spec_lib._fragment_mz_df = frag_mass_df.copy()\n", + "df = speclib_to_single_df(spec_lib, min_frag_mz=300, max_frag_mz=1800)\n", + "assert (df.FragmentMz>=300).all()\n", + "assert (df.FragmentMz<=1800).all()\n", + "df = speclib_to_single_df(spec_lib, min_frag_mz=200, min_frag_nAA=3)\n", + "assert (df.FragmentNumber>=3).all()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| hide\n", + "\n", + "import tempfile\n", + "from alphabase.peptide.fragment import create_fragment_mz_dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "11it [00:00, 3152.75it/s]\n", + "100%|██████████| 6/6 [00:00<00:00, 67.25it/s]\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ModifiedPeptidePrecursorChargeTr_recalibratedIonMobilityStrippedPeptidePrecursorMzFragmentTypeFragmentMzRelativeIntensityFragmentChargeFragmentLossTypeFragmentNumber
0_AS(Phospho)GHCEWMKYR_2101ASGHCEWMKYR724.285972y1376.5275551.0000001noloss10
1_AS(Phospho)GHCEWMKYR_2101ASGHCEWMKYR724.285972y1278.5506590.9288231H3PO410
2_AS(Phospho)GHCEWMKYR_2101ASGHCEWMKYR724.285972b1273.4529930.9251201noloss10
3_AS(Phospho)GHCEWMKYR_2101ASGHCEWMKYR724.285972y1209.5291950.8786811noloss9
4_AS(Phospho)GHCEWMKYR_2101ASGHCEWMKYR724.285972y1152.5077320.8372571noloss8
.......................................
127_ASGHCEWMAAR_2101ASGHCEWMAAR609.760775b771.2878850.6721601noloss7
128_ASGHCEWMAAR_2101ASGHCEWMAAR609.760775y763.3555710.6652471noloss6
129_ASGHCEWMAAR_2101ASGHCEWMAAR609.760775y634.3129780.5527891noloss5
130_ASGHCEWMAAR_2101ASGHCEWMAAR609.760775b585.2085720.5099961noloss6
131_ASGHCEWMAAR_2101ASGHCEWMAAR609.760775b456.1659790.3975381noloss5
\n", + "

132 rows × 12 columns

\n", + "
" + ], + "text/plain": [ + " ModifiedPeptide PrecursorCharge Tr_recalibrated IonMobility \\\n", + "0 _AS(Phospho)GHCEWMKYR_ 2 10 1 \n", + "1 _AS(Phospho)GHCEWMKYR_ 2 10 1 \n", + "2 _AS(Phospho)GHCEWMKYR_ 2 10 1 \n", + "3 _AS(Phospho)GHCEWMKYR_ 2 10 1 \n", + "4 _AS(Phospho)GHCEWMKYR_ 2 10 1 \n", + ".. ... ... ... ... \n", + "127 _ASGHCEWMAAR_ 2 10 1 \n", + "128 _ASGHCEWMAAR_ 2 10 1 \n", + "129 _ASGHCEWMAAR_ 2 10 1 \n", + "130 _ASGHCEWMAAR_ 2 10 1 \n", + "131 _ASGHCEWMAAR_ 2 10 1 \n", + "\n", + " StrippedPeptide PrecursorMz FragmentType FragmentMz RelativeIntensity \\\n", + "0 ASGHCEWMKYR 724.285972 y 1376.527555 1.000000 \n", + "1 ASGHCEWMKYR 724.285972 y 1278.550659 0.928823 \n", + "2 ASGHCEWMKYR 724.285972 b 1273.452993 0.925120 \n", + "3 ASGHCEWMKYR 724.285972 y 1209.529195 0.878681 \n", + "4 ASGHCEWMKYR 724.285972 y 1152.507732 0.837257 \n", + ".. ... ... ... ... ... \n", + "127 ASGHCEWMAAR 609.760775 b 771.287885 0.672160 \n", + "128 ASGHCEWMAAR 609.760775 y 763.355571 0.665247 \n", + "129 ASGHCEWMAAR 609.760775 y 634.312978 0.552789 \n", + "130 ASGHCEWMAAR 609.760775 b 585.208572 0.509996 \n", + "131 ASGHCEWMAAR 609.760775 b 456.165979 0.397538 \n", + "\n", + " FragmentCharge FragmentLossType FragmentNumber \n", + "0 1 noloss 10 \n", + "1 1 H3PO4 10 \n", + "2 1 noloss 10 \n", + "3 1 noloss 9 \n", + "4 1 noloss 8 \n", + ".. ... ... ... \n", + "127 1 noloss 7 \n", + "128 1 noloss 6 \n", + "129 1 noloss 5 \n", + "130 1 noloss 6 \n", + "131 1 noloss 5 \n", + "\n", + "[132 rows x 12 columns]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#| hide\n", + "repeat = 10\n", + "charged_frag_types = ['b_z1','y_z1','y_modloss_z1']\n", + "precursor_df = pd.DataFrame({\n", + " 'sequence': ['ASGHCEWMKYR']*repeat+['ASGHCEWMAAR'],\n", + " 'mods': ['Acetyl@Protein N-term;Carbamidomethyl@C;Oxidation@M']*repeat+[''],\n", + " 'mod_sites': ['0;4;8']*repeat+[''],\n", + " 'nAA': 11,\n", + " 'NCE': 20,\n", + " 'instrument': 'QE',\n", + " 'rt_pred': 10,\n", + " 'charge': 2,\n", + " 'protein_name': 'unknown',\n", + " 'mobility_pred': 1,\n", + "})\n", + "precursor_df.loc[0,['mods','mod_sites']] = ['Phospho@S','2']\n", + "frag_mass_df = create_fragment_mz_dataframe(precursor_df, charged_frag_types)\n", + "spec_lib = SpecLibBase(charged_frag_types)\n", + "spec_lib._precursor_df = precursor_df\n", + "spec_lib._fragment_intensity_df = frag_mass_df.copy()\n", + "spec_lib._fragment_mz_df = frag_mass_df.copy()\n", + "speclib_sdf = speclib_to_single_df(spec_lib)\n", + "with tempfile.TemporaryFile('w+') as f:\n", + " translate_to_tsv(spec_lib, f, batch_size=2, multiprocessing=False)\n", + " f.seek(0)\n", + " ddf = pd.read_csv(f, sep=\"\\t\")\n", + "assert len(ddf) == len(speclib_sdf)\n", + "assert ddf.StrippedPeptide.values[0] == speclib_sdf.StrippedPeptide.values[0]\n", + "assert ddf.StrippedPeptide.values[-1] == speclib_sdf.StrippedPeptide.values[-1]\n", + "assert ddf.PrecursorCharge.dtype==np.int\n", + "ddf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.3 ('base')", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 0c3e662c8b337110328749401397e392407246c1 Mon Sep 17 00:00:00 2001 From: "Zeng, Wen-Feng" Date: Thu, 13 Oct 2022 10:54:27 +0200 Subject: [PATCH 13/52] docs from scoring --- docs/constants/aa.html | 19 +- docs/constants/element.html | 19 +- docs/constants/isotope.html | 19 +- docs/constants/modification.html | 19 +- docs/index.html | 19 +- docs/io/hdf.html | 19 +- docs/peptide/fragment.html | 19 +- docs/peptide/mass_calc.html | 19 +- docs/peptide/mobility.html | 19 +- docs/peptide/precursor.html | 28 +- docs/protein/fasta.html | 19 +- docs/protein/test_fasta.html | 19 +- docs/psm_reader/alphapept_reader.html | 19 +- docs/psm_reader/dia_psm_reader.html | 19 +- docs/psm_reader/maxquant_reader.html | 19 +- docs/psm_reader/msfragger_reader.html | 19 +- docs/psm_reader/pfind_reader.html | 19 +- docs/psm_reader/psm_reader.html | 19 +- docs/scoring/fdr.html | 19 +- docs/scoring/feature_extraction_base.html | 50 +- docs/scoring/ml_scoring_base.html | 50 +- docs/search.json | 17 +- docs/sitemap.xml | 56 +- docs/spectral_library/decoy_library.html | 19 +- docs/spectral_library/library_base.html | 23 +- docs/spectral_library/translate.html | 3548 +++++++++++++++++ docs/statistics/regression.html | 117 +- .../figure-html/cell-10-output-1.png | Bin 0 -> 27481 bytes .../figure-html/cell-5-output-1.png | Bin 28935 -> 0 bytes .../figure-html/cell-9-output-1.png | Bin 0 -> 28568 bytes docs/utils.html | 19 +- docs/yaml_utils.html | 19 +- nbdev_nbs/sidebar.yml | 1 + 33 files changed, 3885 insertions(+), 404 deletions(-) create mode 100644 docs/spectral_library/translate.html create mode 100644 docs/statistics/regression_files/figure-html/cell-10-output-1.png delete mode 100644 docs/statistics/regression_files/figure-html/cell-5-output-1.png create mode 100644 docs/statistics/regression_files/figure-html/cell-9-output-1.png diff --git a/docs/constants/aa.html b/docs/constants/aa.html index 74e8c097..55f434e2 100644 --- a/docs/constants/aa.html +++ b/docs/constants/aa.html @@ -146,19 +146,7 @@ alphabase - - + @@ -403,6 +401,41 @@

On this page

Regression


+

source

+ +
+

right_open_tricubic

+
+
 right_open_tricubic (x)
+
+

tricubic weight kernel which weights assigns 1 to values x > 0

+
+

source

+
+
+

left_open_tricubic

+
+
 left_open_tricubic (x)
+
+

tricubic weight kernel which weights assigns 1 to values x < 0

+
+

source

+
+
+

tricubic

+
+
 tricubic (x)
+
+

tricubic weight kernel

+
+

source

+
+
+

apply_kernel

+
+
 apply_kernel (w)
+
+

source

@@ -527,22 +560,50 @@

LOESSRegression.pr

Application example

-
def noisy_1d(x):
-    y = np.sin(x)
-    y_err = np.random.normal(y,0.5)
-    return y + y_err + 0.5 * x
-
-x_train = np.linspace(0,15,200)
-y_train = noisy_1d(x_train)
-
-x_test = np.linspace(0,15,200)
-y_test = LOESSRegression().fit(x_train, y_train).predict(x_test)
-
-plt.scatter(x_train,y_train)
-plt.plot(x_test,y_test,c='r')
-plt.show()
+
np.set_printoptions(formatter={'float': lambda x: "{0:0.3f}".format(x)})
+def noisy_1d(x):
+    y = np.sin(x)
+    y_err = np.random.normal(y,0.5)
+    return y + y_err + 0.5 * x
+
+x_train = np.linspace(0,15,200)
+y_train = noisy_1d(x_train)
+
+x_test = np.linspace(0,15,200)
+y_test = LOESSRegression().fit(x_train, y_train).predict(x_test)
+
+plt.scatter(x_train,y_train)
+plt.plot(x_test,y_test,c='r')
+plt.show()
+
+

+
+
+
+
+

Weight function

+
+
fig, axs = plt.subplots(nrows=3,sharex=True)
+
+x = np.linspace(-2,2,200)
+y = left_open_tricubic(x)
+axs[0].plot(x,y)
+axs[0].set_title('left_open_tricubic')
+
+x = np.linspace(-2,2,200)
+y = tricubic(x)
+axs[1].plot(x,y)
+axs[1].set_title('tricubic')
+
+x = np.linspace(-2,2,200)
+y = right_open_tricubic(x)
+axs[2].plot(x,y)
+axs[2].set_title('right_open_tricubic')
+
+fig.tight_layout()
+plt.show()
-

+

diff --git a/docs/statistics/regression_files/figure-html/cell-10-output-1.png b/docs/statistics/regression_files/figure-html/cell-10-output-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f210bbaf1f4fe632619894085ae4a068520fdacb GIT binary patch literal 27481 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 literal 0 HcmV?d00001 diff --git a/docs/utils.html b/docs/utils.html index dc548ee9..ea26a341 100644 --- a/docs/utils.html +++ b/docs/utils.html @@ -80,19 +80,7 @@ alphabase - - +