├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── conda_recipe ├── bld.bat ├── build.sh └── meta.yaml ├── environment.yml ├── pdLSR ├── __init__.py ├── aggregation.py ├── auxiliary.py ├── demo │ ├── GCN4_twofield.tsv │ └── pdLSR_demo.ipynb ├── docstring.py ├── fitting.py ├── functions.py ├── lmfit_setup.py ├── pdLSR.py └── version.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | .DS_Store 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # IPython Notebook 37 | .ipynb_checkpoints -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Michelle L. Gill 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include MANIFEST.in 4 | include environment.yml 5 | include requirements.txt 6 | recursive-include pdLSR/demo * -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pdLSR: Pandas-aware least squares regression 2 | 3 | ## Overview 4 | 5 | `pdLSR` is a library for performing least squares regression. It attempts to seamlessly incorporate this task in a Pandas-focused workflow. Input data are expected in dataframes, and multiple regressions can be performed using functionality similar to Pandas `groupby`. Results are returned as grouped dataframes and include best-fit parameters, statistics, residuals, and more. The results can be easily visualized using [`seaborn`](https://github.com/mwaskom/seaborn). 6 | 7 | `pdLSR` currently utilizes [`lmfit`](https://github.com/lmfit/lmfit-py), a flexible and powerful library for least squares minimization, which in turn, makes use of `scipy.optimize.leastsq`. I began using `lmfit` because it is one of the few libraries that supports non-linear least squares regression, which is commonly used in the natural sciences. I also like the flexibility it offers for testing different modeling scenarios and the variety of assessment statistics it provides. However, I found myself writing many `for` loops to perform regressions on groups of data and aggregate the resulting output. Simplification of this task was my inspiration for writing `pdLSR`. 8 | 9 | `pdLSR` is related to libraries such as [`statsmodels`](http://statsmodels.sourceforge.net) and [`scikit-learn`](http://scikit-learn.org/stable/) that provide linear regression functions that operate on dataframes. However, these libraries don't support grouping operations on dataframes and don't aggregate output into dataframes. Supporting `statsmodels` and `scikit-learn` in the future is being considered. (And pull requests adding this functionality would be welcome.) 10 | 11 | Some additional 'niceties' associated with the input of parameters and equations have also been incorporated. `pdLSR` also utilizes multithreading for the calculation of confidence intervals, as this process is time consuming when there are more than a few groups. 12 | 13 | ## Setup 14 | 15 | ### Dependencies 16 | 17 | The following libraries are required for `pdLSR`: 18 | 19 | * numpy 20 | * pandas 21 | * lmfit 22 | * multiprocess 23 | 24 | [`multiprocess`](https://github.com/uqfoundation/multiprocess) is a fork of Python's `multiprocessing` library that provides more robust multithreading. I found that this library is required for multithreading to work with `pdLSR`. Both `multiprocess` and `lmfit` will install automatically from `pip` or `conda` (see below). 25 | 26 | For plotting, `matplotlib` is required and `seaborn` is recommended. 27 | 28 | `pdLSR` works with Python 2 and 3. 29 | 30 | ### Installation and Demo 31 | [![Binder](http://mybinder.org/badge.svg)](http://mybinder.org/repo/mlgill/pdLSR) 32 | 33 | The preferred method for installing `pdLSR` and all of its dependencies is to use the `conda` or `pip` package managers. 34 | 35 | * For conda: `conda install -c mlgill pdlsr` -- unfortunately conda seems to require lowercase names for packages 36 | * For pip: `pip install pdLSR` 37 | 38 | However it can also be installed manually by cloning the repo into your `PYTHONPATH`. 39 | 40 | There is a demo notebook that can be executed locally or live from GitHub using [mybinder.org](http://mybinder.org/repo/mlgill/pdLSR). After clicking the badge at the top of this section, navigate to `pdLSR --> demo --> pdLSR_demo.ipynb` and everything should be setup to execute the demo in a browser. No installation required! 41 | 42 | ## Documentation 43 | 44 | The functions of `pdLSR` are documented within the code, but currently the best single source for using `pdLSR` is the [demo notebook](https://github.com/mlgill/pdLSR/blob/master/pdLSR/demo/pdLSR_demo.ipynb). Developing stand-alone documentation is a future goal. 45 | 46 | 47 | -------------------------------------------------------------------------------- /conda_recipe/bld.bat: -------------------------------------------------------------------------------- 1 | "%PYTHON%" setup.py install 2 | if errorlevel 1 exit 1 3 | 4 | :: Add more build steps here, if they are necessary. 5 | 6 | :: See 7 | :: http://docs.continuum.io/conda/build.html 8 | :: for a list of environment variables that are set during the build process. 9 | -------------------------------------------------------------------------------- /conda_recipe/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | $PYTHON setup.py install 4 | 5 | # Add more build steps here, if they are necessary. 6 | 7 | # See 8 | # http://docs.continuum.io/conda/build.html 9 | # for a list of environment variables that are set during the build process. 10 | -------------------------------------------------------------------------------- /conda_recipe/meta.yaml: -------------------------------------------------------------------------------- 1 | about: 2 | summary: 'pdLSR: Pandas-aware least squares regression.' 3 | home: https://github.com/mlgill/pdLSR/ 4 | license: 'BSD (3-clause)' 5 | 6 | package: 7 | name: pdlsr 8 | version: '0.3.6' 9 | 10 | source: 11 | git_tag: '0.3.6' 12 | git_url: https://github.com/mlgill/pdLSR 13 | 14 | # patches: 15 | # List any patch files here 16 | # - fix.patch 17 | 18 | build: 19 | # noarch_python: True 20 | # preserve_egg_dir: True 21 | # entry_points: 22 | # Put any entry points (scripts to be generated automatically) here. The 23 | # syntax is module:function. For example 24 | 25 | # If this is a new build for the same version, increment the build 26 | # number. If you do not include this key, it defaults to 0. 27 | number: '0' 28 | 29 | requirements: 30 | build: 31 | - python 32 | - setuptools 33 | - numpy 34 | - pandas >=0.18.1 35 | - lmfit >=0.9.3 36 | - multiprocess >=0.70.4 37 | 38 | run: 39 | - python 40 | - numpy 41 | - pandas >=0.18.1 42 | - lmfit >=0.9.3 43 | - multiprocess >=0.70.4 44 | 45 | test: 46 | # Python imports 47 | imports: 48 | - pdLSR 49 | 50 | # commands: 51 | # You can put test commands to be run here. Use this to test that the 52 | # entry points work. 53 | 54 | 55 | # You can also put a file called run_test.py in the recipe that will be run 56 | # at test time. 57 | 58 | # requires: 59 | # Put any additional test requirements here. For example 60 | # - nose 61 | 62 | # See 63 | # http://docs.continuum.io/conda/build.html for 64 | # more information about meta.yaml 65 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | # Running 'conda env create' in this directory will install necessary dependencies 2 | name: pdLSR 3 | 4 | channels: 5 | - mlgill 6 | 7 | dependencies: 8 | - notebook>=4.2.1 9 | - matplotlib>=1.5.1 10 | - numpy>=1.11.1 11 | - pandas>=0.18.1 12 | - seaborn>=0.7.0 13 | - multiprocess>=0.70.4 14 | - lmfit>=0.9.3 15 | 16 | -------------------------------------------------------------------------------- /pdLSR/__init__.py: -------------------------------------------------------------------------------- 1 | from .functions import * 2 | from .pdLSR import pdLSR 3 | 4 | from .docstring import DOCSTRING 5 | from .version import VERSION 6 | 7 | __doc__ = DOCSTRING 8 | __version__ = VERSION 9 | 10 | 11 | # TODOs: 12 | # [ ] make confidence interval calculation work without multithreading 13 | # [ ] add statsmodels and maybe scikit-learn functionality 14 | # [ ] add .sortlevel(axis=0) to all tables? 15 | # [ ] function to copy demo notebook and data for demo? 16 | # [ ] seaborn factgrid plotting function? 17 | -------------------------------------------------------------------------------- /pdLSR/aggregation.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import re 4 | 5 | 6 | def get_results(fitobj_df, params, sigma): 7 | """Aggregate fit parameters into a dataframe. 8 | 9 | Parameters 10 | ---------- 11 | fitobj_df : dataframe 12 | A dataframe containing minimizer objects and confidence intervals. 13 | params : list 14 | List of parameter names. 15 | sigma : float or list 16 | Confidence interval value or list. 17 | 18 | Returns 19 | ------- 20 | results : dataframe 21 | A dataframe of parameter values and error estimates.""" 22 | 23 | # Get the parameters 24 | parameters = pd.concat( [fitobj_df.fitobj.apply(lambda x: pd.Series({'{}_value'.format(par_name):x.params[par_name].value, 25 | '{}_stderr'.format(par_name):x.params[par_name].stderr} 26 | ) 27 | ) 28 | for par_name in params ], axis=1) 29 | 30 | # Get the confidence interval bound for each % ci entered 31 | mask = pd.notnull(fitobj_df.ciobj) 32 | 33 | # Extract the confidence intervals as a dictionary 34 | conf_intervals = pd.concat([fitobj_df.loc[mask, 'ciobj'].apply(lambda x: pd.Series({(par_name, val[1][0], val[0]):val[1][1] 35 | for val in enumerate(x[par_name])})) 36 | for par_name in params], axis=1) 37 | 38 | # Drop the 0.0 sigma value which is just the parameter value itself 39 | conf_intervals = conf_intervals.loc[:, (slice(None), sigma)] 40 | 41 | conf_intervals.sort_index(axis=1, inplace=True) 42 | 43 | # Rename the integer level in preparation for joining to sigma value 44 | max_val = conf_intervals.columns.get_level_values(-1).max() 45 | mid_val = int((max_val+1)/2) 46 | column_mapper = dict([(x,'lo') for x in range(mid_val)] + 47 | [(x,'hi') for x in range(mid_val+1, max_val+1)]) 48 | conf_intervals = conf_intervals.rename_axis(column_mapper, axis=1) 49 | 50 | # Set new indices 51 | level_2 = ['ci{}_{}'.format(x,y) for x,y in 52 | zip(conf_intervals.columns.get_level_values(1), 53 | conf_intervals.columns.get_level_values(2))] 54 | 55 | ci_index = pd.MultiIndex.from_tuples(zip(conf_intervals.columns.get_level_values(0), level_2)) 56 | 57 | conf_intervals.columns = ci_index 58 | 59 | # Fix column names for parameters 60 | colnames = [re.search(r"""([^_]+)_([^_]+)""", col) for col in parameters.columns] 61 | parameters.columns = pd.MultiIndex.from_tuples([(col.group(1), col.group(2)) for col in colnames]) 62 | 63 | # Combine parameters and confidence intervals 64 | results = pd.merge(parameters, conf_intervals, left_index=True, right_index=True) 65 | 66 | results.sortlevel(axis=1, inplace=True) 67 | 68 | results = (results 69 | .sortlevel(level=1, axis=1, ascending=False) 70 | .sortlevel(level=0, axis=1, sort_remaining=False) 71 | ) 72 | 73 | return results 74 | 75 | 76 | def get_stats(fitobj_df, stats_df, stats_cols=['chisqr', 'redchi', 'aic', 'bic']): 77 | """Aggregate regression statistics into a dataframe. 78 | 79 | Parameters 80 | ---------- 81 | fitobj_df : series 82 | A series containing minimizer objects. 83 | stats_df : dataframe 84 | A dataframe of previously calculated stats (dof, npar, etc.). 85 | stats_cols : list 86 | A list containing the names of fit attributes to add to the 87 | nascent stats dataframe. 88 | 89 | Returns 90 | ------- 91 | stats_df : dataframe 92 | A dataframe with new""" 93 | 94 | for dat in stats_cols: 95 | lambda_str = 'lambda x: x.{}'.format(dat) 96 | stats_df[dat] = fitobj_df.apply(eval(lambda_str)) 97 | 98 | stats_df = stats_df.rename(columns={'redchi':'r_chisqr'}) 99 | 100 | return stats_df 101 | 102 | 103 | def get_covar(fitobj_df): 104 | """Converte the covariance matrices into an indexed dataframe. 105 | 106 | Parameters 107 | ---------- 108 | fitobj_df : dataframe 109 | A dataframe containing minimizer objects. 110 | 111 | Returns 112 | ------- 113 | covar_df : dataframe 114 | A dataframe containing indexed covariance matrices.""" 115 | 116 | predict_list = list() 117 | 118 | index_names = fitobj_df.index.names 119 | 120 | covar_list = list() 121 | 122 | # Iterate through each group, get the covariance matrix and put into 123 | # a dataframe 124 | for index in fitobj_df.index.values: 125 | covar = fitobj_df.loc[index, 'fitobj'].covar 126 | 127 | index_array = pd.Index([index]*4, name=index_names) 128 | 129 | # Handle the indexing of the matrix 130 | nrow,ncol = covar.shape 131 | row_index, col_index = np.unravel_index(list(range(nrow*ncol)), (nrow, ncol)) 132 | 133 | covar_list.append(pd.DataFrame({'row': row_index, 134 | 'col': col_index, 135 | 'covar':covar.flatten()}, 136 | index=index_array, 137 | columns=['row', 'col', 'covar'])) 138 | 139 | return pd.concat(covar_list, axis=0) 140 | 141 | 142 | -------------------------------------------------------------------------------- /pdLSR/auxiliary.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | 4 | # Loss function for error calculation 5 | def error_function(par, func, xdata, ydata=None, yerr=None): 6 | """Calculate the error as the sum of square of residuals 7 | 8 | Parameters 9 | ---------- 10 | par : list 11 | Parameter values. 12 | func : function 13 | Function being minimized. 14 | xdata : array 15 | The independent data 16 | ydata : array 17 | The dependent data [optional, will predict if omitted] 18 | yerr : array 19 | The error estimats [optional] 20 | 21 | Returns 22 | ------- 23 | data : array 24 | Either the predicted value or the residuals squared 25 | (with or without error weighting) are returned.""" 26 | 27 | # The calculated value 28 | ycalc = func(par, xdata) 29 | 30 | if ydata is None: 31 | # Calculation only 32 | return ycalc 33 | elif yerr is None: 34 | # Error minimization 35 | return (ycalc - ydata)**2 36 | else: 37 | # Error minimization with weights 38 | return (ycalc - ydata)**2/yerr**2 39 | 40 | 41 | # # Function to expand arrays contained in a single dataframe row 42 | # def expand_df(data, name, groupby): 43 | 44 | # if len(groupby) == 1: 45 | # groupby = groupby[0] 46 | 47 | # if isinstance(data, pd.DataFrame): 48 | # dfexpand = pd.concat([pd.Series(np.array(x[1].values[0]), 49 | # index=pd.Index([x[0]]*len(x[1].values[0]), name=groupby), 50 | # name=name) 51 | # for x in data.iterrows()], axis=0) 52 | # else: 53 | # dfexpand = pd.concat([pd.Series(np.array(x[1]), 54 | # index=pd.Index([x[0]]*len(x[1]), name=groupby), 55 | # name=name) 56 | # for x in data.iteritems()], axis=0) 57 | 58 | # if not(isinstance(dfexpand, pd.DataFrame)): 59 | # dfexpand = pd.DataFrame(dfexpand) 60 | 61 | # return dfexpand 62 | -------------------------------------------------------------------------------- /pdLSR/demo/GCN4_twofield.tsv: -------------------------------------------------------------------------------- 1 | resi field time intensity 2 | 51 14.1 0.004 1624.219428 3 | 51 14.1 0.008 1491.46728 4 | 51 14.1 0.024 1022.717456 5 | 51 14.1 0.064 448.038543 6 | 51 14.1 0.096 270.116745 7 | 51 14.1 0.144 148.671651 8 | 51 14.1 0.208 103.322608 9 | 52 14.1 0.004 1614.38584 10 | 52 14.1 0.008 1500.47205 11 | 52 14.1 0.024 985.92621 12 | 52 14.1 0.064 386.851286 13 | 52 14.1 0.096 219.239347 14 | 52 14.1 0.144 68.960746 15 | 52 14.1 0.208 14.111535 16 | 53 14.1 0.004 2357.652142 17 | 53 14.1 0.008 2294.012362 18 | 53 14.1 0.024 1611.529523 19 | 53 14.1 0.064 704.344013 20 | 53 14.1 0.096 392.049951 21 | 53 14.1 0.144 153.4915 22 | 53 14.1 0.208 42.171928 23 | 54 14.1 0.004 2215.698179 24 | 54 14.1 0.008 2040.452443 25 | 54 14.1 0.024 1370.101093 26 | 54 14.1 0.064 548.204232 27 | 54 14.1 0.096 319.674016 28 | 54 14.1 0.144 102.403781 29 | 54 14.1 0.208 33.466528 30 | 55 14.1 0.004 1974.503833 31 | 55 14.1 0.008 1845.272298 32 | 55 14.1 0.024 1331.737496 33 | 55 14.1 0.064 543.606759 34 | 55 14.1 0.096 286.865765 35 | 55 14.1 0.144 111.000526 36 | 55 14.1 0.208 34.291668 37 | 56 14.1 0.004 4869.023926 38 | 56 14.1 0.008 4409.967476 39 | 56 14.1 0.024 3568.78758 40 | 56 14.1 0.064 2058.462342 41 | 56 14.1 0.096 1386.000648 42 | 56 14.1 0.144 762.086314 43 | 56 14.1 0.208 348.150072 44 | 51 18.8 0.004 22966.75 45 | 51 18.8 0.008 20038.089844 46 | 51 18.8 0.024 13337.732422 47 | 51 18.8 0.064 5731.903809 48 | 51 18.8 0.096 3503.782715 49 | 51 18.8 0.144 2126.470461 50 | 51 18.8 0.208 1499.782715 51 | 52 18.8 0.004 20754.453125 52 | 52 18.8 0.008 19324.9375 53 | 52 18.8 0.024 11172.667969 54 | 52 18.8 0.064 3904.419188 55 | 52 18.8 0.096 1414.189453 56 | 52 18.8 0.144 374.720398 57 | 52 18.8 0.208 134.238191 58 | 53 18.8 0.004 40397.410156 59 | 53 18.8 0.008 35302.859375 60 | 53 18.8 0.024 23655.773438 61 | 53 18.8 0.064 8829.749023 62 | 53 18.8 0.096 4182.798828 63 | 53 18.8 0.144 1312.683105 64 | 53 18.8 0.208 102.139418 65 | 54 18.8 0.004 36969.855469 66 | 54 18.8 0.008 31838.675781 67 | 54 18.8 0.024 21150.509766 68 | 54 18.8 0.064 7017.300781 69 | 54 18.8 0.096 3204.052977 70 | 54 18.8 0.144 826.136719 71 | 54 18.8 0.208 35.124383 72 | 55 18.8 0.004 31568.398438 73 | 55 18.8 0.008 29320.955078 74 | 55 18.8 0.024 18283.560547 75 | 55 18.8 0.064 7039.672852 76 | 55 18.8 0.096 3348.133301 77 | 55 18.8 0.144 851.069215 78 | 55 18.8 0.208 252.933168 79 | 56 18.8 0.004 76501.726562 80 | 56 18.8 0.008 66075.71875 81 | 56 18.8 0.024 53366.742188 82 | 56 18.8 0.064 27280.933594 83 | 56 18.8 0.096 17233.021484 84 | 56 18.8 0.144 7355.9375 85 | 56 18.8 0.208 2922.692383 -------------------------------------------------------------------------------- /pdLSR/demo/pdLSR_demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# pdLSR: Pandas-aware least squares regression" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": { 14 | "ExecuteTime": { 15 | "end_time": "2016-07-29T19:33:32.949323", 16 | "start_time": "2016-07-29T19:33:30.553216" 17 | }, 18 | "collapsed": false 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "from __future__ import print_function\n", 23 | "import os\n", 24 | "import inspect\n", 25 | "\n", 26 | "import numpy as np\n", 27 | "import pandas as pd\n", 28 | "\n", 29 | "pd.set_option('display.float_format', lambda x: '%.2f' % x)\n", 30 | "np.set_printoptions(precision=2)\n", 31 | "\n", 32 | "import matplotlib.pyplot as plt\n", 33 | "import seaborn as sns\n", 34 | "\n", 35 | "sns.set_context('talk')\n", 36 | "sns.set_palette('dark')\n", 37 | "sns.set_style('ticks')\n", 38 | "\n", 39 | "%matplotlib inline" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 2, 45 | "metadata": { 46 | "ExecuteTime": { 47 | "end_time": "2016-07-29T19:33:33.015878", 48 | "start_time": "2016-07-29T19:33:32.951484" 49 | }, 50 | "collapsed": false 51 | }, 52 | "outputs": [], 53 | "source": [ 54 | "# Import pdLSR\n", 55 | "try:\n", 56 | " # If pdLSR is in PYTHONPATH (or is installed), then use a direct import\n", 57 | " import pdLSR\n", 58 | " \n", 59 | "except ImportError:\n", 60 | " # Attempt to demo pdLSR without installing by importing from directory\n", 61 | " pdLSR_path = '../../pdLSR'\n", 62 | " \n", 63 | " print(\"Module pdLSR was not found in PYTHONPATH. Looking for module in directory '{:s}'\".format(pdLSR_path))\n", 64 | " \n", 65 | " if os.path.exists(pdLSR_path):\n", 66 | " import imp\n", 67 | " pdLSR = imp.load_package('pdLSR', pdLSR_path)\n", 68 | " print(\"Module pdLSR was found in the directory '{:s}' and imported.\".format(pdLSR_path))\n", 69 | " else:\n", 70 | " raise ImportError(\"Module pdLSR could not be found in the directory '{:s}'.\".format(pdLSR_path) + \\\n", 71 | " \"This demonstration will not run until the module is located.\")" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "## Overview\n", 79 | "\n", 80 | "`pdLSR` is a library for performing least squares minimization. It attempts to seamlessly incorporate this task in a Pandas-focused workflow. Input data are expected in dataframes, and multiple regressions can be performed using functionality similar to Pandas `groupby`. Results are returned as grouped dataframes and include best-fit parameters, statistics, residuals, and more. The results can be easily visualized using [`seaborn`](https://github.com/mwaskom/seaborn).\n", 81 | "\n", 82 | "`pdLSR` currently utilizes [`lmfit`](https://github.com/lmfit/lmfit-py), a flexible and powerful library for least squares minimization, which in turn, makes use of `scipy.optimize.leastsq`. I began using `lmfit` because it is one of the few libraries that supports non-linear least squares regression, which is commonly used in the natural sciences. I also like the flexibility it offers for testing different modeling scenarios and the variety of assessment statistics it provides. However, I found myself writing many `for` loops to perform regressions on groups of data and aggregate the resulting output. Simplification of this task was my inspiration for writing `pdLSR`.\n", 83 | "\n", 84 | "`pdLSR` is related to libraries such as [`statsmodels`](http://statsmodels.sourceforge.net) and [`scikit-learn`](http://scikit-learn.org/stable/) that provide linear regression functions that operate on dataframes. However, these libraries don't support grouping operations on dataframes. Supporting `statsmodels` and `scikit-learn` is being considered. (And pull requests adding this functionality would be welcome.)\n", 85 | "\n", 86 | "Some additional 'niceties' associated with the input of parameters and equations have also been incorporated. `pdLSR` also utilizes multithreading for the calculation of confidence intervals, as this process is time consuming when there are more than a few groups." 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "### Input data\n", 94 | "\n", 95 | "This `pdLSR` demonstration utilizes nuclear magnetic resonance (NMR) data acquired at two different magnetic field strengths (14.1 and 18.8 T) on the DNA-binding region of a transcription factor called GCN4. For the purpose of this demonstration, analyzing this data requires determining the rate of exponential decay as a function of time for every amino acid residue at each of two magnetic field strengths. \n", 96 | "\n", 97 | "There are 12 amino acids in the enclosed data set, so using amino acid residue (`resi`) and magnetic field (`field`) as the `groupby` columns results in 24 minimization operations." 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 3, 103 | "metadata": { 104 | "ExecuteTime": { 105 | "end_time": "2016-07-29T19:33:34.623337", 106 | "start_time": "2016-07-29T19:33:34.487474" 107 | }, 108 | "collapsed": false 109 | }, 110 | "outputs": [ 111 | { 112 | "name": "stdout", 113 | "output_type": "stream", 114 | "text": [ 115 | "resi\tfield\ttime\tintensity\r\n", 116 | "51\t14.1\t0.004\t1624.219428\r\n", 117 | "51\t14.1\t0.008\t1491.46728\r\n", 118 | "51\t14.1\t0.024\t1022.717456\r\n", 119 | "51\t14.1\t0.064\t448.038543\r\n", 120 | "51\t14.1\t0.096\t270.116745\r\n", 121 | "51\t14.1\t0.144\t148.671651\r\n", 122 | "51\t14.1\t0.208\t103.322608\r\n", 123 | "52\t14.1\t0.004\t1614.38584\r\n", 124 | "52\t14.1\t0.008\t1500.47205\r\n" 125 | ] 126 | } 127 | ], 128 | "source": [ 129 | "data = pd.read_csv('GCN4_twofield.tsv', sep='\\t')\n", 130 | "! head GCN4_twofield.tsv" 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "metadata": {}, 136 | "source": [ 137 | "### Dataframe input\n", 138 | "\n", 139 | "`pdLSR` uses a dataframe for input as well as a column (or list of columns, called `groupbycols` here) for the `groupby` operation prior to fitting. A string input corresponding to the independent data (`xname`) and dependent data (`yname`) are required. Errors for weighting during fitting (`yerr`, not used here) can also be used." 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 4, 145 | "metadata": { 146 | "ExecuteTime": { 147 | "end_time": "2016-07-29T19:33:35.324769", 148 | "start_time": "2016-07-29T19:33:35.317962" 149 | }, 150 | "collapsed": true 151 | }, 152 | "outputs": [], 153 | "source": [ 154 | "groupby = ['resi', 'field']\n", 155 | "xname = 'time'\n", 156 | "yname = 'intensity'\n", 157 | "yerr = None" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "### Minimization equation\n", 165 | "\n", 166 | "The function format required by `pdLSR` is similar that described in the [`lmfit` documentation](http://lmfit.github.io/lmfit-py/fitting.html#writing-a-fitting-function). Generally speaking, all fit parameters must appear first and they must be contained in a single variable or dictionary that is then unpacked inside the function. The independent (x-) data is next and then the dependent (y-) data.\n", 167 | "\n", 168 | "`pdLSR` contains a separate function that calculates the regression error, so the input function need only calculate the expected value based on input parameters.\n", 169 | "\n", 170 | "Common equations are provided in the `function` module. Currently, an exponential decay and a linear function are provided.\n", 171 | "\n", 172 | "This demo will be using the exponential decay function, whose parameters are: \n", 173 | "\n", 174 | "$I_{(t)} = I_{(0)} \\space e^{(-R * t)}$ \n", 175 | "\n", 176 | "where $I_{(0)}$ is the initial intensity, $I_{(t)}$ is the intensity at time $t$, and $R$ is the exponential decay rate. \n", 177 | "\n", 178 | "Here is what the exponential decay function looks like: " 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 5, 184 | "metadata": { 185 | "ExecuteTime": { 186 | "end_time": "2016-07-29T19:33:36.471055", 187 | "start_time": "2016-07-29T19:33:36.462244" 188 | }, 189 | "collapsed": false 190 | }, 191 | "outputs": [ 192 | { 193 | "name": "stdout", 194 | "output_type": "stream", 195 | "text": [ 196 | "def exponential_decay(par, xdata):\n", 197 | " \"\"\"An exponential decay function for minimization.\n", 198 | "\n", 199 | " Parameters\n", 200 | " ----------\n", 201 | " par : list or dictionary\n", 202 | " Contains the intitial intensity ('inten')\n", 203 | " and decay rate ('rate'),\n", 204 | " order must be as stated.\n", 205 | " xdata : array\n", 206 | " An array of dependent data.\n", 207 | "\n", 208 | " Returns\n", 209 | " -------\n", 210 | " ydata : array\n", 211 | " An exponential decay calculated from the parameters\n", 212 | " and the xdata.\"\"\"\n", 213 | " \n", 214 | " # Parse multiple input parameter\n", 215 | " # formats for intensity, rate \n", 216 | " if hasattr(par,'valuesdict'):\n", 217 | " # lmfit parameter format\n", 218 | " var = par.valuesdict()\n", 219 | " inten = var['inten']\n", 220 | " rate = var['rate']\n", 221 | " elif hasattr(par,'keys'):\n", 222 | " # dict format\n", 223 | " inten = par['inten']\n", 224 | " rate = par['rate']\n", 225 | " else:\n", 226 | " # array/list/tuple format\n", 227 | " inten = par[0]\n", 228 | " rate = par[1]\n", 229 | "\n", 230 | " # Calculate the y-data from the parameters\n", 231 | " return inten * np.exp(-1*rate*xdata)\n", 232 | "\n" 233 | ] 234 | } 235 | ], 236 | "source": [ 237 | "exponential_decay = pdLSR.functions.exponential_decay\n", 238 | "print(inspect.getsource(exponential_decay))" 239 | ] 240 | }, 241 | { 242 | "cell_type": "markdown", 243 | "metadata": {}, 244 | "source": [ 245 | "### Parameters\n", 246 | "\n", 247 | "Parameters are usually input as a dictionary, although dataframe input will soon be an option. The list of parameters must be the same as the order in which they are unpacked by the minimization function. The keys for each parameter follows the format used by [`lmfit`](). \n", 248 | "\n", 249 | "Parameter starting values can be a single value or a list. If a single value is entered, it is used for each measurement. If a list or array is entered (see the intensity values below), it must be the same length as the number of groups." 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": 6, 255 | "metadata": { 256 | "ExecuteTime": { 257 | "end_time": "2016-07-29T19:33:37.771222", 258 | "start_time": "2016-07-29T19:33:37.751578" 259 | }, 260 | "collapsed": false 261 | }, 262 | "outputs": [], 263 | "source": [ 264 | "params = [{'name':'inten', \n", 265 | " 'value':np.asarray(data.groupby(groupby)[yname].max()), \n", 266 | " 'vary':True},\n", 267 | " {'name':'rate', \n", 268 | " 'value':20.0, \n", 269 | " 'vary':True}]" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "### Other inputs\n", 277 | "\n", 278 | "The only minimization method (`method`) currently supported is Levenberg-Marquardt ('leastsq'). Other methods will be added in the future. \n", 279 | "\n", 280 | "The confidence interval(s) (`sigma`) to be calculated can be entered as a single value or a list. The default is 95% (0.95). The number of threads (`threads`) to use for confidence interval calculation will be automatically calculated if not explicitly set." 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": 7, 286 | "metadata": { 287 | "ExecuteTime": { 288 | "end_time": "2016-07-29T19:33:38.650540", 289 | "start_time": "2016-07-29T19:33:38.645550" 290 | }, 291 | "collapsed": true 292 | }, 293 | "outputs": [], 294 | "source": [ 295 | "minimizer_kwargs = {'params':params,\n", 296 | " 'method':'leastsq',\n", 297 | " 'sigma':0.95,\n", 298 | " 'threads':None}" 299 | ] 300 | }, 301 | { 302 | "cell_type": "markdown", 303 | "metadata": {}, 304 | "source": [ 305 | "## Regression and prediction\n", 306 | "\n", 307 | "Performing the regression is quite simple--just call the class `pdNLS` with input parameters set and then use the `fit` method. A best-fit line can be calculated using the `predict` method, which will create a dataframe called `model` to store the results." 308 | ] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "execution_count": 8, 313 | "metadata": { 314 | "ExecuteTime": { 315 | "end_time": "2016-07-29T19:33:46.380265", 316 | "start_time": "2016-07-29T19:33:44.524837" 317 | }, 318 | "collapsed": false 319 | }, 320 | "outputs": [ 321 | { 322 | "name": "stderr", 323 | "output_type": "stream", 324 | "text": [ 325 | "/Volumes/Files/miniconda/envs/scienv2/lib/python2.7/site-packages/pandas/core/indexing.py:1294: PerformanceWarning: indexing past lexsort depth may impact performance.\n", 326 | " return self._getitem_tuple(key)\n" 327 | ] 328 | } 329 | ], 330 | "source": [ 331 | "fit_data = pdLSR.pdLSR(data, exponential_decay, groupby, \n", 332 | " xname, yname,\n", 333 | " minimizer='lmfit',\n", 334 | " minimizer_kwargs=minimizer_kwargs)\n", 335 | "fit_data.fit()\n", 336 | "fit_data.predict()" 337 | ] 338 | }, 339 | { 340 | "cell_type": "markdown", 341 | "metadata": {}, 342 | "source": [ 343 | "## Results\n", 344 | "\n", 345 | "`pdLSR` creates five output tables:\n", 346 | "\n", 347 | "* **data** for the input data, calculated data, and residuals\n", 348 | "* **results** that contains the best-fit parameters and estimation of their error\n", 349 | "* **stats** for statistics related to the regression, such as chi-squared and AIC\n", 350 | "* **model** that contains a best-fit line created by the `predict` method\n", 351 | "* **covar** that contains the covariance matrices\n", 352 | "\n", 353 | "Here is part of the **results** table for a single residue and magnetic field:" 354 | ] 355 | }, 356 | { 357 | "cell_type": "code", 358 | "execution_count": 9, 359 | "metadata": { 360 | "ExecuteTime": { 361 | "end_time": "2016-07-29T19:33:46.415677", 362 | "start_time": "2016-07-29T19:33:46.382731" 363 | }, 364 | "collapsed": false 365 | }, 366 | "outputs": [ 367 | { 368 | "data": { 369 | "text/html": [ 370 | "
\n", 371 | "\n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | "
timeintensityycalcresiduals
resifield
5114.100.001624.221585.1839.04
14.100.011491.471465.3526.12
14.100.021022.721070.03-47.32
14.100.06448.04487.57-39.53
14.100.10270.12259.9910.13
14.100.14148.67101.2347.44
14.100.21103.3228.7874.54
\n", 443 | "
" 444 | ], 445 | "text/plain": [ 446 | " time intensity ycalc residuals\n", 447 | "resi field \n", 448 | "51 14.10 0.00 1624.22 1585.18 39.04\n", 449 | " 14.10 0.01 1491.47 1465.35 26.12\n", 450 | " 14.10 0.02 1022.72 1070.03 -47.32\n", 451 | " 14.10 0.06 448.04 487.57 -39.53\n", 452 | " 14.10 0.10 270.12 259.99 10.13\n", 453 | " 14.10 0.14 148.67 101.23 47.44\n", 454 | " 14.10 0.21 103.32 28.78 74.54" 455 | ] 456 | }, 457 | "execution_count": 9, 458 | "metadata": {}, 459 | "output_type": "execute_result" 460 | } 461 | ], 462 | "source": [ 463 | "resi = 51\n", 464 | "field = 14.1\n", 465 | "\n", 466 | "fit_data.data.loc[(resi, field)]" 467 | ] 468 | }, 469 | { 470 | "cell_type": "markdown", 471 | "metadata": {}, 472 | "source": [ 473 | "The **results** table contains best-fit parameters, their standard errors, and confidence intervals." 474 | ] 475 | }, 476 | { 477 | "cell_type": "code", 478 | "execution_count": 10, 479 | "metadata": { 480 | "ExecuteTime": { 481 | "end_time": "2016-07-29T19:33:48.054978", 482 | "start_time": "2016-07-29T19:33:48.008791" 483 | }, 484 | "collapsed": false 485 | }, 486 | "outputs": [ 487 | { 488 | "data": { 489 | "text/html": [ 490 | "
\n", 491 | "\n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | "
intenrate
valuestderrci0.95_loci0.95_hivaluestderrci0.95_loci0.95_hi
resifield
5114.101714.8037.871656.391833.2619.651.0217.9122.46
18.8024122.68689.0322988.0925953.7421.641.5818.8325.55
5214.101778.9017.871746.801814.0023.350.4922.3924.36
18.8023889.26232.8723351.2424329.2629.960.7928.2731.59
\n", 573 | "
" 574 | ], 575 | "text/plain": [ 576 | " inten rate \\\n", 577 | " value stderr ci0.95_lo ci0.95_hi value stderr ci0.95_lo \n", 578 | "resi field \n", 579 | "51 14.10 1714.80 37.87 1656.39 1833.26 19.65 1.02 17.91 \n", 580 | " 18.80 24122.68 689.03 22988.09 25953.74 21.64 1.58 18.83 \n", 581 | "52 14.10 1778.90 17.87 1746.80 1814.00 23.35 0.49 22.39 \n", 582 | " 18.80 23889.26 232.87 23351.24 24329.26 29.96 0.79 28.27 \n", 583 | "\n", 584 | " \n", 585 | " ci0.95_hi \n", 586 | "resi field \n", 587 | "51 14.10 22.46 \n", 588 | " 18.80 25.55 \n", 589 | "52 14.10 24.36 \n", 590 | " 18.80 31.59 " 591 | ] 592 | }, 593 | "execution_count": 10, 594 | "metadata": {}, 595 | "output_type": "execute_result" 596 | } 597 | ], 598 | "source": [ 599 | "fit_data.results.head(n=4)" 600 | ] 601 | }, 602 | { 603 | "cell_type": "markdown", 604 | "metadata": {}, 605 | "source": [ 606 | "The **stats** table contains statistics for each of the regressions:\n", 607 | "\n", 608 | "* Number of observations (`nobs`)\n", 609 | "* Number of fit parameters (`npar`)\n", 610 | "* Degrees of freedom (`dof`)\n", 611 | "* Chi-squared (`chisqr`)\n", 612 | "* Reduced chi-squared (`redchi`)\n", 613 | "* Akaike information criterion (`aic`)\n", 614 | "* Bayesian information criterion (`bic`)" 615 | ] 616 | }, 617 | { 618 | "cell_type": "code", 619 | "execution_count": 11, 620 | "metadata": { 621 | "ExecuteTime": { 622 | "end_time": "2016-07-29T19:33:49.022417", 623 | "start_time": "2016-07-29T19:33:48.984502" 624 | }, 625 | "collapsed": false, 626 | "scrolled": false 627 | }, 628 | "outputs": [ 629 | { 630 | "data": { 631 | "text/html": [ 632 | "
\n", 633 | "\n", 634 | " \n", 635 | " \n", 636 | " \n", 637 | " \n", 638 | " \n", 639 | " \n", 640 | " \n", 641 | " \n", 642 | " \n", 643 | " \n", 644 | " \n", 645 | " \n", 646 | " \n", 647 | " \n", 648 | " \n", 649 | " \n", 650 | " \n", 651 | " \n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | " \n", 694 | " \n", 695 | " \n", 696 | " \n", 697 | " \n", 698 | " \n", 699 | " \n", 700 | " \n", 701 | " \n", 702 | "
nobsnpardofchisqrr_chisqraicbic
resifield
5114.1072546189329.099237865.82116.27116.16
18.807255181283778043.331036256755608.67197.67197.56
5214.107252013267.84402653.5794.3494.23
18.80725184908562051.3936981712410.28174.34174.23
\n", 703 | "
" 704 | ], 705 | "text/plain": [ 706 | " nobs npar dof chisqr r_chisqr aic bic\n", 707 | "resi field \n", 708 | "51 14.10 7 2 5 46189329.09 9237865.82 116.27 116.16\n", 709 | " 18.80 7 2 5 5181283778043.33 1036256755608.67 197.67 197.56\n", 710 | "52 14.10 7 2 5 2013267.84 402653.57 94.34 94.23\n", 711 | " 18.80 7 2 5 184908562051.39 36981712410.28 174.34 174.23" 712 | ] 713 | }, 714 | "execution_count": 11, 715 | "metadata": {}, 716 | "output_type": "execute_result" 717 | } 718 | ], 719 | "source": [ 720 | "fit_data.stats.head(n=4)" 721 | ] 722 | }, 723 | { 724 | "cell_type": "markdown", 725 | "metadata": {}, 726 | "source": [ 727 | "It is also easy to access a single covariance matrix for calculations." 728 | ] 729 | }, 730 | { 731 | "cell_type": "code", 732 | "execution_count": 12, 733 | "metadata": { 734 | "ExecuteTime": { 735 | "end_time": "2016-07-29T19:33:50.168387", 736 | "start_time": "2016-07-29T19:33:50.144311" 737 | }, 738 | "collapsed": false 739 | }, 740 | "outputs": [ 741 | { 742 | "data": { 743 | "text/html": [ 744 | "
\n", 745 | "\n", 746 | " \n", 747 | " \n", 748 | " \n", 749 | " \n", 750 | " \n", 751 | " \n", 752 | " \n", 753 | " \n", 754 | " \n", 755 | " \n", 756 | " \n", 757 | " \n", 758 | " \n", 759 | " \n", 760 | " \n", 761 | " \n", 762 | " \n", 763 | " \n", 764 | " \n", 765 | " \n", 766 | " \n", 767 | " \n", 768 | " \n", 769 | " \n", 770 | " \n", 771 | " \n", 772 | " \n", 773 | " \n", 774 | " \n", 775 | " \n", 776 | " \n", 777 | " \n", 778 | " \n", 779 | " \n", 780 | " \n", 781 | " \n", 782 | " \n", 783 | " \n", 784 | " \n", 785 | " \n", 786 | " \n", 787 | " \n", 788 | " \n", 789 | "
rowcolcovar
resifield
5114.10001434.41
14.100125.19
14.101025.19
14.10111.04
\n", 790 | "
" 791 | ], 792 | "text/plain": [ 793 | " row col covar\n", 794 | "resi field \n", 795 | "51 14.10 0 0 1434.41\n", 796 | " 14.10 0 1 25.19\n", 797 | " 14.10 1 0 25.19\n", 798 | " 14.10 1 1 1.04" 799 | ] 800 | }, 801 | "execution_count": 12, 802 | "metadata": {}, 803 | "output_type": "execute_result" 804 | } 805 | ], 806 | "source": [ 807 | "fit_data.covar.loc[(resi, field)]" 808 | ] 809 | }, 810 | { 811 | "cell_type": "code", 812 | "execution_count": 13, 813 | "metadata": { 814 | "ExecuteTime": { 815 | "end_time": "2016-07-29T19:33:50.732955", 816 | "start_time": "2016-07-29T19:33:50.669698" 817 | }, 818 | "collapsed": false 819 | }, 820 | "outputs": [ 821 | { 822 | "data": { 823 | "text/plain": [ 824 | "array([[ 1.43e+03, 2.52e+01],\n", 825 | " [ 2.52e+01, 1.04e+00]])" 826 | ] 827 | }, 828 | "execution_count": 13, 829 | "metadata": {}, 830 | "output_type": "execute_result" 831 | } 832 | ], 833 | "source": [ 834 | "fit_data.pivot_covar().loc[(resi, field)].values" 835 | ] 836 | }, 837 | { 838 | "cell_type": "markdown", 839 | "metadata": {}, 840 | "source": [ 841 | "## Visualization\n", 842 | "\n", 843 | "The results are easy to visualize in facet plots with Seaborn. The facet plots will be easier to see if the intensities are normalized first." 844 | ] 845 | }, 846 | { 847 | "cell_type": "code", 848 | "execution_count": 14, 849 | "metadata": { 850 | "ExecuteTime": { 851 | "end_time": "2016-07-29T19:33:51.541218", 852 | "start_time": "2016-07-29T19:33:51.535998" 853 | }, 854 | "collapsed": false 855 | }, 856 | "outputs": [], 857 | "source": [ 858 | "fit_itensities = fit_data.results.loc[:,('inten','value')]" 859 | ] 860 | }, 861 | { 862 | "cell_type": "code", 863 | "execution_count": 15, 864 | "metadata": { 865 | "ExecuteTime": { 866 | "end_time": "2016-07-29T19:33:52.144234", 867 | "start_time": "2016-07-29T19:33:52.128131" 868 | }, 869 | "collapsed": false 870 | }, 871 | "outputs": [], 872 | "source": [ 873 | "fit_data.data['intensity'] = fit_data.data.intensity.div(fit_itensities)\n", 874 | "fit_data.model['ycalc'] = fit_data.model.ycalc.div(fit_itensities)" 875 | ] 876 | }, 877 | { 878 | "cell_type": "code", 879 | "execution_count": 16, 880 | "metadata": { 881 | "ExecuteTime": { 882 | "end_time": "2016-07-29T19:33:56.943356", 883 | "start_time": "2016-07-29T19:33:55.411272" 884 | }, 885 | "collapsed": false 886 | }, 887 | "outputs": [ 888 | { 889 | "data": { 890 | "image/png": "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\nYII=\n", 891 | "text/plain": [ 892 | "" 893 | ] 894 | }, 895 | "metadata": {}, 896 | "output_type": "display_data" 897 | } 898 | ], 899 | "source": [ 900 | "plot_data = pd.concat([fit_data.data, \n", 901 | " fit_data.model], axis=0).reset_index()\n", 902 | "\n", 903 | "colors = sns.color_palette()\n", 904 | "\n", 905 | "palette = {14.1:colors[0], 18.8:colors[2]}\n", 906 | "\n", 907 | "grid = sns.FacetGrid(plot_data, col='resi', hue='field', palette=palette,\n", 908 | " col_wrap=3, size=2.0, aspect=0.75, \n", 909 | " sharey=True, despine=True)\n", 910 | "\n", 911 | "\n", 912 | "grid.map(plt.plot, 'xcalc', 'ycalc', marker='', ls='-', lw=1.0)\n", 913 | "grid.map(plt.plot, 'time', 'intensity', marker='o', ms=5, ls='')\n", 914 | "\n", 915 | "grid.set(xticks=np.linspace(0.05, 0.25, 5),\n", 916 | " ylim=(-0.1, 1.05))\n", 917 | "\n", 918 | "ax = grid.axes[0]\n", 919 | "legend = ax.get_legend_handles_labels()\n", 920 | "ax.legend(legend[0][2:], legend[1][2:], loc=0, frameon=True)\n", 921 | "\n", 922 | "f = plt.gcf()\n", 923 | "f.set_size_inches(12,8)\n", 924 | "f.subplots_adjust(wspace=0.2, hspace=0.25)" 925 | ] 926 | }, 927 | { 928 | "cell_type": "markdown", 929 | "metadata": {}, 930 | "source": [ 931 | "Just for fun, here's a bar graph of the decay rates determined from NLS." 932 | ] 933 | }, 934 | { 935 | "cell_type": "code", 936 | "execution_count": 17, 937 | "metadata": { 938 | "ExecuteTime": { 939 | "end_time": "2016-07-29T19:33:58.092881", 940 | "start_time": "2016-07-29T19:33:57.108581" 941 | }, 942 | "collapsed": false 943 | }, 944 | "outputs": [ 945 | { 946 | "data": { 947 | "image/png": "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\n", 948 | "text/plain": [ 949 | "" 950 | ] 951 | }, 952 | "metadata": {}, 953 | "output_type": "display_data" 954 | } 955 | ], 956 | "source": [ 957 | "plot_data = (fit_data.results\n", 958 | " .sort_index(axis=1)\n", 959 | " .loc[:,('rate',['value','stderr'])]\n", 960 | " )\n", 961 | "plot_data.columns = plot_data.columns.droplevel(0)\n", 962 | "plot_data.reset_index(inplace=True)\n", 963 | "\n", 964 | "fig = plt.figure()\n", 965 | "fig.set_size_inches(7,5)\n", 966 | "ax = plt.axes()\n", 967 | "\n", 968 | "palette = [colors[0], colors[2]]\n", 969 | "\n", 970 | "for pos, (field, dat) in enumerate(plot_data.groupby('field')):\n", 971 | " _ = dat.plot('resi', 'value', yerr='stderr',\n", 972 | " kind='bar', label=field, color=palette[pos],\n", 973 | " position=(-pos)+1, ax=ax, width=0.4)\n", 974 | " \n", 975 | "ax.set_ylabel('decay rate (s$^{-1}$)')\n", 976 | "ax.set_xlabel('residue')\n", 977 | "ax.set_xlim(ax.get_xlim()[0]-0.5, ax.get_xlim()[1])\n", 978 | "plt.xticks(rotation=0)\n", 979 | "\n", 980 | "sns.despine()\n", 981 | "plt.tight_layout()" 982 | ] 983 | } 984 | ], 985 | "metadata": { 986 | "kernelspec": { 987 | "display_name": "Python 2", 988 | "language": "python", 989 | "name": "python2" 990 | }, 991 | "language_info": { 992 | "codemirror_mode": { 993 | "name": "ipython", 994 | "version": 2 995 | }, 996 | "file_extension": ".py", 997 | "mimetype": "text/x-python", 998 | "name": "python", 999 | "nbconvert_exporter": "python", 1000 | "pygments_lexer": "ipython2", 1001 | "version": "2.7.12" 1002 | } 1003 | }, 1004 | "nbformat": 4, 1005 | "nbformat_minor": 0 1006 | } 1007 | -------------------------------------------------------------------------------- /pdLSR/docstring.py: -------------------------------------------------------------------------------- 1 | DOCSTRING = """\ 2 | pdLSR 3 | by Michelle L. Gill 4 | 5 | `pdLSR` is a library for performing least squares regression. It attempts to 6 | seamlessly incorporate this task in a Pandas-focused workflow. Input data 7 | are expected in dataframes, and multiple regressions can be performed using 8 | functionality similar to Pandas `groupby`. Results are returned as grouped 9 | dataframes and include best-fit parameters, statistics, residuals, and more. 10 | 11 | `pdLSR` has been tested on python 2.7, 3.4, and 3.5. It requires Numpy, 12 | Pandas, multiprocess (https://github.com/uqfoundation/multiprocess), and 13 | lmfit (https://github.com/lmfit/lmfit-py). All dependencies are installable 14 | via pip or conda (see README.md). 15 | 16 | A demonstration notebook is provided in the `demo` directory or the demo 17 | can be run via GitHub (see README.md). 18 | 19 | """ 20 | -------------------------------------------------------------------------------- /pdLSR/fitting.py: -------------------------------------------------------------------------------- 1 | import lmfit 2 | import pandas as pd 3 | import numpy as np 4 | from .auxiliary import error_function 5 | 6 | import multiprocess as multiprocessing 7 | 8 | 9 | def get_minimizer(index, fitobj_df, data_df, params_df, 10 | xname, yname, yerr): 11 | """Create an indexed series of minimizer functions. 12 | 13 | Parameters 14 | ---------- 15 | index : Pandas index 16 | A grouped index for the dataframe. 17 | fitobj_df : dataframe 18 | A dataframe of the fit objects. 19 | data_df : dataframe 20 | A dataframe of the input data. 21 | params_df : dataframe 22 | A dataframe of the input parameters. 23 | xname : string 24 | Name of the data_df column containing the xdata. 25 | yname : string 26 | Name of the data_df column contain the ydata. 27 | yerr : string 28 | Name of the data_df column containg the yerror 29 | estimates (optional). 30 | 31 | 32 | Returns 33 | ------- 34 | minimizer : series 35 | A pandas series containing all the indexed minimizer objects.""" 36 | 37 | # Creates the lmfit minimizer object 38 | 39 | minimizer = list() 40 | 41 | for i in index: 42 | 43 | model_eq = fitobj_df.loc[i, 'model_eq'] 44 | 45 | xdata = data_df.loc[i, xname].values 46 | ydata = data_df.loc[i, yname].values 47 | 48 | if yerr is not None: 49 | yerrors = data_df.loc[i, yerr].values 50 | fcn_args = '(model_eq, xdata, ydata, yerrors)' 51 | else: 52 | fcn_args = '(model_eq, xdata, ydata)' 53 | 54 | 55 | params = params_df.loc[i] 56 | params = [ lmfit.Parameter(**params.loc[x].to_dict()) 57 | for x in params.index.levels[0] 58 | ] 59 | 60 | minimizer.append(lmfit.Minimizer(error_function, 61 | params, 62 | fcn_args=eval(fcn_args)) 63 | ) 64 | 65 | return pd.Series(minimizer, index=index, name='minimizer') 66 | 67 | 68 | def get_confidence_interval(fitobj_df, mask, sigma, threads=None): 69 | """Calculate the confidence intervals from the minimizer objects. 70 | 71 | Parameters 72 | ---------- 73 | fitobj_df : dataframe 74 | A dataframe of the fit objects. 75 | mask : array/series 76 | A boolean mask indicating if the confidence intervals 77 | can't be calculated for any of the groups. 78 | sigma : float or list 79 | The confidence intervals to be calculated. 80 | threads : int or None 81 | The number of threads to use for multithreaded 82 | calculation of confidence intervals. 83 | 84 | Returns 85 | ------- 86 | ci_obj : series 87 | A series of confidence interval objects.""" 88 | 89 | def _calc_ci(arr): 90 | 91 | ciobj = list() 92 | for m,f in zip(arr[:,0], arr[:,1]): 93 | try: 94 | res = lmfit.conf_interval(m, f, sigmas=sigma) 95 | except: 96 | res = np.NaN 97 | 98 | ciobj.append(res) 99 | 100 | return np.array(ciobj) 101 | 102 | if threads is None: 103 | threads = multiprocessing.cpu_count() 104 | 105 | fitobj_arr = np.array_split(fitobj_df[['minimizer','fitobj']].values, threads) 106 | 107 | pool = multiprocessing.Pool() 108 | ciobj = np.array(pool.map(_calc_ci, fitobj_arr)).flatten() 109 | 110 | return pd.Series(ciobj, index=fitobj_df.index) 111 | -------------------------------------------------------------------------------- /pdLSR/functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def linear(par, xdata): 4 | """A linear function for minimization. 5 | 6 | Parameters 7 | ---------- 8 | par : list or dictionary 9 | Contains the slope and intercept, 10 | order must be as stated. 11 | xdata : array 12 | An array of dependent data. 13 | 14 | Returns 15 | ------- 16 | ydata : array 17 | A line calculated from the parameters 18 | and the xdata.""" 19 | 20 | # Parse multiple input parameter 21 | # formats for slope, intercept 22 | if hasattr(par,'valuesdict'): 23 | # lmfit parameter format 24 | var = par.valuesdict() 25 | slope = var['slope'] 26 | intercept = var['intercept'] 27 | elif hasattr(par,'keys'): 28 | # dict format 29 | slope = par['slope'] 30 | intercept = par['intercept'] 31 | else: 32 | # array/list/tuple format 33 | slope = par[0] 34 | intercept = par[1] 35 | 36 | # Calculate the y-data from the parameters 37 | return intercept + slope * xdata 38 | 39 | 40 | def exponential_decay(par, xdata): 41 | """An exponential decay function for minimization. 42 | 43 | Parameters 44 | ---------- 45 | par : list or dictionary 46 | Contains the intitial intensity ('inten') 47 | and decay rate ('rate'), 48 | order must be as stated. 49 | xdata : array 50 | An array of dependent data. 51 | 52 | Returns 53 | ------- 54 | ydata : array 55 | An exponential decay calculated from the parameters 56 | and the xdata.""" 57 | 58 | # Parse multiple input parameter 59 | # formats for intensity, rate 60 | if hasattr(par,'valuesdict'): 61 | # lmfit parameter format 62 | var = par.valuesdict() 63 | inten = var['inten'] 64 | rate = var['rate'] 65 | elif hasattr(par,'keys'): 66 | # dict format 67 | inten = par['inten'] 68 | rate = par['rate'] 69 | else: 70 | # array/list/tuple format 71 | inten = par[0] 72 | rate = par[1] 73 | 74 | # Calculate the y-data from the parameters 75 | return inten * np.exp(-1*rate*xdata) -------------------------------------------------------------------------------- /pdLSR/lmfit_setup.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | def lmfit_params(self, kwargs_input): 4 | """Assign lmfit minimizer parameters. 5 | 6 | Parameters 7 | ---------- 8 | kwargs_input : dictionary 9 | Dictionary of input parameters for lmfit minimizer. 10 | 11 | Returns 12 | ------- 13 | pdLSR : class 14 | Assigns necessary attributes to the pdLSR class.""" 15 | 16 | kwargs = {'method':'leastsq', 17 | 'sigma':'0.95', 18 | 'threads':None} 19 | 20 | kwargs.update(kwargs_input) 21 | 22 | if 'params' in kwargs.keys(): 23 | self._params = kwargs['params'] 24 | else: 25 | raise AttributeError('"params" are a required input for the lmfit minimizer.') 26 | 27 | # Fitting and confidence interval methods 28 | self._method = kwargs['method'] 29 | 30 | if self._method is not 'leastsq': 31 | raise NotImplementedError('The only lmfit minimization method currently implemented is leastsq.') 32 | 33 | self._sigma = kwargs['sigma'] 34 | 35 | if not hasattr(self._sigma, '__iter__'): 36 | self._sigma = [self._sigma] 37 | 38 | self._threads = kwargs['threads'] 39 | 40 | if (isinstance(self._params, list) and isinstance(self._params[0], dict)): 41 | # params is a list of dictionaries 42 | params_df = convert_param_dict_to_df(self._params, 43 | self._ngroups, 44 | self._index) 45 | 46 | elif isinstance(self._params, pd.DataFrame): 47 | # params is a dataframe 48 | params_df = self._params 49 | 50 | else: 51 | raise AttributeError('Parameters should be either a list of dictionaries or a dataframe.') 52 | 53 | self._params_df = params_df.fillna(method='ffill') 54 | 55 | # Setup parameter dataframe 56 | self._paramnames = [x['name'] for x in self._params] 57 | 58 | return self 59 | 60 | 61 | def convert_param_dict_to_df(params, ngroups, index): 62 | """Converts a dictionary of parameter values to a dataframe. 63 | 64 | Parameters 65 | ---------- 66 | params : dictionary 67 | Dictionary of parameter values for lmfit. 68 | ngroups : integer 69 | Number of groups being fit. 70 | index : Pandas index 71 | The index for the grouping of the dataframe. 72 | 73 | Returns 74 | ------- 75 | params_df : dataframe 76 | An indexed dataframe with parameter values extracted.""" 77 | 78 | # Unique list of variable names 79 | var_names = map(lambda x: x['name'], params) 80 | 81 | # Expanded list of names and all properties to create a 82 | # multi-level column index 83 | column_list = [(x['name'], y) 84 | for x in params 85 | for y in x.keys() 86 | ] 87 | 88 | column_index = pd.MultiIndex.from_tuples(column_list, 89 | sortorder=None) 90 | 91 | # Create a dataframe from the indexes and fill it 92 | params_df = pd.DataFrame(index=index, columns=column_index) 93 | 94 | # Fill df by iterating over the parameter name index 95 | # and then each of its keys 96 | for var in enumerate(params_df.columns.levels[0]): 97 | for key in params_df.loc[:,var[1]]: 98 | params_df[(var[1],key)] = params[var[0]][key] 99 | 100 | return params_df -------------------------------------------------------------------------------- /pdLSR/pdLSR.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | from six import string_types 4 | 5 | from .fitting import get_minimizer, get_confidence_interval 6 | from .aggregation import get_results, get_stats, get_covar 7 | from .lmfit_setup import lmfit_params 8 | 9 | 10 | class pdLSR(object): 11 | 12 | """pdLSR is a class that performs least-squares regression using 13 | Pandas-aware features such as dataframe input and groupby functionality. 14 | 15 | Parameters 16 | ---------- 17 | data : dataframe 18 | Input data containing dependent, independent, and optional 19 | error estimates. 20 | model_eq : function 21 | The function that will be minimized during regression. 22 | groupby : list or string 23 | The name of a column (or a list of columns) to use for grouping 24 | during regression. 25 | xname : string 26 | The column name of the independent variable. Currently only 27 | a single independent variable is supported. 28 | yname : string 29 | The column name of the dependent variable. Currently only 30 | a single dependent variable is supported. 31 | yerr : string or None 32 | The column name of the error estimates (optional). Currently 33 | only a single column of error estimates is supported. 34 | minimizer : string 35 | The name of the minimizer function. 'lmfit' is currently 36 | supported. 37 | minimizer_kwargs : dictionary 38 | The arguments for the minimizer. 39 | 40 | For 'lmfit': 41 | Required inputs are: 42 | 'params': the parameters. 43 | Optional inputs are: 44 | 'method': only 'leastsq' is supported. 45 | 'sigma': which is the confidence interval 46 | (number or list, default is 0.95). 47 | 'threads': the number of threads to use for calculation of the 48 | confidence intervals (default is None which will cause 49 | automatic calculation). 50 | 51 | Returns 52 | ------- 53 | pdLSR : class 54 | The pdLSR class contains the `fit` and `predict` methods to 55 | perform minimization and calculate a best fit line based on 56 | input values. 57 | """ 58 | 59 | def __init__(self, data, model_eq, groupby, 60 | xname, yname, yerr=None, 61 | minimizer='lmfit', 62 | **minimizer_kwargs): 63 | 64 | """Initialize the pdLSR class and check that inputs are valid.""" 65 | 66 | # Ensure the selected columns aren't hidden in the index 67 | data = data.reset_index() 68 | 69 | # Setup the groupby columns 70 | if ( (not hasattr(groupby, '__iter__')) | isinstance(groupby, string_types) ): 71 | groupby = [groupby] 72 | 73 | for col in groupby: 74 | if col not in data.columns: 75 | raise IndexError('{} not in input data columns or index.'.format(col)) 76 | 77 | self._groupby = groupby 78 | self._ngroupby = len(groupby) 79 | 80 | # Dependent and independent variables 81 | self._xname = xname 82 | self._yname = yname 83 | self._yerr = yerr 84 | 85 | self._datacols = [self._xname, self._yname] 86 | 87 | if self._yerr is not None: 88 | self._datacols += [self._yerr] 89 | 90 | # Check that all columns are in the data 91 | for col in self._datacols: 92 | if col not in data.columns: 93 | raise IndexError('{} not in input data columns or index.'.format(col)) 94 | 95 | # Unique index information 96 | index = ( data[self._groupby + [self._xname]] 97 | .groupby(self._groupby) 98 | .max() 99 | .index 100 | ) 101 | 102 | self._index = index 103 | self._ngroups = data.index.shape[0] 104 | 105 | # Get the arguments for the minimizer 106 | kwargs_input = minimizer_kwargs['minimizer_kwargs'] 107 | 108 | if minimizer=='lmfit': 109 | self = lmfit_params(self, kwargs_input) 110 | else: 111 | raise NotImplementedError('The only minimizer currently implemented is lmfit.') 112 | 113 | # Append the dataframe of data 114 | self.data = ( data 115 | [self._groupby + self._datacols] 116 | .set_index(self._groupby) 117 | ) 118 | 119 | # Dataframe to hold the fitting objects 120 | self._fitobj = pd.DataFrame(index=self._index) 121 | self._fitobj['model_eq'] = model_eq 122 | self._fitobj['model_eq'] = self._fitobj.model_eq.fillna(method='ffill') 123 | 124 | return 125 | 126 | 127 | def _predict(self, xcalc, xnum): 128 | 129 | """Perform prediction on input independent data or on a custom range.""" 130 | 131 | xname = self._xname 132 | 133 | if xcalc: 134 | if xcalc=='global': 135 | xmin = self.data[xname].min() 136 | xmax = self.data[xname].max() 137 | xdata = np.linspace(xmin, xmax, xnum) 138 | 139 | predict_list = list() 140 | 141 | index_names = self._fitobj.index.names 142 | 143 | for index in self._fitobj.index.values: 144 | 145 | if xcalc: 146 | if xcalc=='local': 147 | xmin = self.data.loc[index, xname].min() 148 | xmax = self.data.loc[index, xname].max() 149 | xdata = np.linspace(xmin, xmax, xnum) 150 | else: 151 | xdata = (self 152 | .data 153 | .loc[index, xname] 154 | .squeeze() 155 | .values 156 | ) 157 | 158 | model_eq = (self 159 | ._fitobj 160 | .loc[index, 'model_eq'] 161 | ) 162 | 163 | params = (self 164 | .results 165 | .sortlevel(axis=1) 166 | .loc[index, (slice(None), ['value'])] 167 | .squeeze() 168 | .values 169 | ) 170 | 171 | ydata = model_eq(params, xdata) 172 | 173 | index_array = pd.Index([index]*len(xdata), name=index_names) 174 | 175 | if xcalc: 176 | predict_data = pd.DataFrame({'xcalc':xdata, 'ycalc':ydata}, 177 | index=index_array) 178 | else: 179 | predict_data = pd.Series(ydata, name='ycalc', 180 | index=index_array) 181 | 182 | predict_list.append(predict_data) 183 | 184 | return pd.concat(predict_list, axis=0) 185 | 186 | 187 | def fit(self): 188 | """Perform regression using the minimizer and input parameters of choice. 189 | 190 | Returns 191 | ------- 192 | pdLSR.data : dataframe 193 | This dataframe contains the input data, predicted values, and 194 | residuals. 195 | pdLSR.results : dataframe 196 | This dataframe contains the regression parameters. 197 | pdLSR.stats : dataframe 198 | This dataframe contains the evaluation parameters provided by 199 | the minimizer. 200 | pdLSR.covar : dataframe 201 | This dataframe contains the covariance matrix. 202 | """ 203 | 204 | # Perform the minimization 205 | self._fitobj['minimizer'] = get_minimizer(self._index, self._fitobj, self.data, self._params_df, 206 | self._xname, self._yname, self._yerr) 207 | 208 | self._fitobj['fitobj'] = ( self._fitobj.minimizer 209 | .apply(lambda x: x.minimize(method=self._method)) 210 | ) 211 | 212 | self.data = self.data.sortlevel(axis=0) 213 | 214 | # Create the stats dataframe 215 | self.stats = pd.DataFrame(index=self._index) 216 | 217 | # dof calculations for confidence intervals 218 | groupby = list(range(self._ngroupby)) 219 | 220 | if len(groupby)==1: 221 | groupby = groupby[0] 222 | 223 | self.stats['nobs'] = ( self 224 | .data 225 | .groupby(level=groupby) 226 | .size() 227 | ) 228 | 229 | self.stats['npar'] = len(self._params_df.columns.levels[0]) 230 | self.stats['dof'] = self.stats.nobs - self.stats.npar 231 | 232 | # Get the confidence intervals 233 | mask = self.stats.npar > 1 234 | if mask.sum() > 0: 235 | self._fitobj['ciobj'] = get_confidence_interval(self._fitobj, 236 | mask, 237 | self._sigma, 238 | self._threads) 239 | else: 240 | self._fitobj['ciobj'] = np.NaN 241 | 242 | 243 | # The results 244 | self.results = ( get_results(self._fitobj, 245 | self._paramnames, 246 | self._sigma) 247 | .sortlevel(axis=0) 248 | ) 249 | 250 | # Predict the y values and calculate residuals 251 | self.data['ycalc'] = self._predict(None, None) 252 | self.data['residuals'] = self.data[self._yname] - self.data['ycalc'] 253 | 254 | # The remaining statistics 255 | self.stats = ( get_stats(self._fitobj.fitobj, 256 | self.stats) 257 | .sortlevel(axis=0) 258 | ) 259 | 260 | # The covariance table 261 | self.covar = get_covar(self._fitobj) 262 | 263 | return 264 | 265 | 266 | def predict(self, xcalc='global', xnum=20): 267 | """Calculate expected values based on interpolated independent data. 268 | 269 | Parameters 270 | ---------- 271 | xcalc : string 272 | 'global' or 'local' indicates whether to use the global min/max 273 | for dependent data or just those from the current group. 274 | 'global' is the default. 275 | xnum : int 276 | Number of points to determine, default is 20. 277 | 278 | Returns 279 | ------- 280 | pdLSR.model : dataframe 281 | An indexed dataframe with calculated independent and 282 | dependent data. 283 | """ 284 | 285 | if not hasattr(self, 'results'): 286 | raise AttributeError('The pdLSR object must be fit before predicting.') 287 | 288 | self.model = self._predict(xcalc, xnum) 289 | return 290 | 291 | 292 | def pivot_covar(self): 293 | """Pivot the covariance matrix so column values extend horizontally. 294 | 295 | Parameters 296 | ---------- 297 | pdLSR : class 298 | A pdLSR class with covariance dataframe. 299 | 300 | Returns 301 | ------- 302 | covar_pivot : dataframe 303 | A dataframe pivoted so that column elements of the matrix 304 | are horizonal. Useful for multiplication, etc.""" 305 | 306 | if not hasattr(self, 'covar'): 307 | raise AttributeError('The pdLSR object does not contain a covariance matrix dataframe.') 308 | 309 | return (self 310 | .covar 311 | .groupby(level=list(range(self._ngroupby))) 312 | .apply(lambda x: x.pivot(index='row', 313 | columns='col', 314 | values='covar')) 315 | ) 316 | -------------------------------------------------------------------------------- /pdLSR/version.py: -------------------------------------------------------------------------------- 1 | VERSION = '0.3.6' -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | pandas>=0.18.1 3 | lmfit>=0.9.3 4 | multiprocess>=0.70.4 5 | matplotlib>=1.5.1 6 | seaborn>=0.7.0 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from pdLSR import DOCSTRING, VERSION 4 | 5 | DESCRIPTION = 'pdLSR: Pandas-aware least squares regression.' 6 | LONG_DESCRIPTION = DOCSTRING 7 | 8 | DISTNAME = 'pdLSR' 9 | MAINTAINER = 'Michelle Gill' 10 | MAINTAINER_EMAIL = 'michelle@michellelynngill.com' 11 | URL = 'https://github.com/mlgill/pdLSR' 12 | LICENSE = 'BSD (3-clause)' 13 | DOWNLOAD_URL = 'https://github.com/mlgill/pdLSR' 14 | 15 | try: 16 | from setuptools import setup 17 | _has_setuptools = True 18 | except ImportError: 19 | from distutils.core import setup 20 | 21 | 22 | def check_dependencies(): 23 | install_requires = [] 24 | 25 | try: 26 | import numpy 27 | 28 | except ImportError: 29 | install_requires.append('numpy') 30 | 31 | 32 | try: 33 | import lmfit 34 | 35 | except ImportError: 36 | install_requires.append('lmfit') 37 | 38 | 39 | try: 40 | import pandas 41 | 42 | except ImportError: 43 | install_requires.append('pandas') 44 | 45 | 46 | try: 47 | import multiprocess 48 | 49 | except ImportError: 50 | install_requires.append('multiprocess') 51 | 52 | 53 | return install_requires 54 | 55 | 56 | if __name__ == "__main__": 57 | 58 | install_requires = check_dependencies() 59 | 60 | setup(name=DISTNAME, 61 | author=MAINTAINER, 62 | author_email=MAINTAINER_EMAIL, 63 | maintainer=MAINTAINER, 64 | maintainer_email=MAINTAINER_EMAIL, 65 | description=DESCRIPTION, 66 | long_description=LONG_DESCRIPTION, 67 | license=LICENSE, 68 | url=URL, 69 | version=VERSION, 70 | download_url=DOWNLOAD_URL, 71 | install_requires=install_requires, 72 | packages=['pdLSR'], 73 | include_package_data = True, 74 | classifiers=[ 75 | 'Intended Audience :: Science/Research', 76 | 'Programming Language :: Python :: 2.7', 77 | 'Programming Language :: Python :: 3.4', 78 | 'Programming Language :: Python :: 3.5', 79 | 'License :: OSI Approved :: BSD License', 80 | 'Topic :: Scientific/Engineering :: Chemistry', 81 | 'Topic :: Scientific/Engineering :: Visualization', 82 | 'Operating System :: POSIX', 83 | 'Operating System :: Unix', 84 | 'Operating System :: MacOS'], 85 | ) --------------------------------------------------------------------------------