From a8a4e0f6fc9c69fa59fc6616e8619124a447b47a Mon Sep 17 00:00:00 2001 From: John Lees Date: Mon, 27 Aug 2018 17:08:58 -0400 Subject: [PATCH 01/14] use gfortran to build .so on install --- setup.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 8da05c8..9204a4b 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,13 @@ import os, sys from setuptools import setup, find_packages -# from numpy.distutils.core import setup, Extension +from setuptools.command.install import install +import subprocess -cmd = 'gfortran ./glmnet_python/GLMnet.f -fPIC -fdefault-real-8 -shared -o ./glmnet_python/GLMnet.so' -os.system(cmd) +class CustomInstallCommand(install): + """Compile shared fortran library""" + def run(self): + command = ['gfortran ./glmnet_python/GLMnet.f -fPIC -fdefault-real-8 -shared -o ./glmnet_python/GLMnet.so'] + subprocess.check_call(command, shell=True) setup(name='glmnet_python', version = '0.2.0', @@ -15,7 +19,9 @@ license = 'GPL-2', packages=['glmnet_python'], install_requires=['joblib>=0.10.3'], - package_data={'glmnet_python': ['*.so', 'glmnet_python/*.so']}, + cmdclass={ + 'install': CustomInstallCommand, + }, classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Science/Research', From 693aec50bae133756fa9000ec2ed8f401842f8d4 Mon Sep 17 00:00:00 2001 From: John Lees Date: Wed, 29 Aug 2018 16:21:25 -0400 Subject: [PATCH 02/14] Change package name --- setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 9204a4b..5272bc0 100644 --- a/setup.py +++ b/setup.py @@ -9,15 +9,15 @@ def run(self): command = ['gfortran ./glmnet_python/GLMnet.f -fPIC -fdefault-real-8 -shared -o ./glmnet_python/GLMnet.so'] subprocess.check_call(command, shell=True) -setup(name='glmnet_python', +setup(name='glmnet_python_pyseer', version = '0.2.0', description = 'Python version of glmnet, from Stanford University', long_description=open('README.md').read(), - url="https://github.com/bbalasub1/glmnet_python", - author = 'Han Fang', - author_email = 'hanfang.cshl@gmail.com', + url="https://github.com/johnlees/glmnet_python", + author = 'Han Fang (modified by John Lees)', + author_email = 'hanfang.cshl@gmail.com (john@johnlees.me)', license = 'GPL-2', - packages=['glmnet_python'], + packages=['glmnet_python_pyseer'], install_requires=['joblib>=0.10.3'], cmdclass={ 'install': CustomInstallCommand, From b3be4dd442da5d24b6b4e8557b74add99270c56f Mon Sep 17 00:00:00 2001 From: John Lees Date: Thu, 30 Aug 2018 10:02:54 -0400 Subject: [PATCH 03/14] Remove precompiled shared library --- .gitignore | 1 + glmnet_python/GLMnet.so | Bin 468107 -> 0 bytes 2 files changed, 1 insertion(+) create mode 100644 .gitignore delete mode 100755 glmnet_python/GLMnet.so diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..941da20 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +glmnet_python/GLMnet.so diff --git a/glmnet_python/GLMnet.so b/glmnet_python/GLMnet.so deleted file mode 100755 index 28ffdcc7920330f7b44746703554c18e69760867..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 468107 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%0DB!SgxuoHBaOh5A;lzHByyA(`A_- zkf%0TfrbFAB%q0sLxOOP`P_7qtom$h4JwfIA+?-RQJ>7DlA>l?054WXePtW3E>wFevhmI5z%=b3 zP$ba*DR?GPla9L5FjGb3VH}wh#fPmY%;4&@hkYnb-YMGVxnnxP?-#Wi42p zG4-lNfK4x+SdzTRR$TbUiU$^GYNG}B$?a$Cw$R$;i6y}j1Bqa8TH(-#TnLA_5U|N1 zOWHZ1oVP+b+bg2yKJBZB6g(zCczQu9Leuu-%E}GERF-s&@jR5lqV$Z1=diCqvN8$z zSDgesAtom?T$7W?UmFo70bWCmdqFhP*3ItHvaZQ$xgJI(xhn}+Y!z+~4`Hh;iy2!b z^}V)AkPK`oJJ#vON_GU3#>#VP#)?|U{uT6Xn#2#Mft?(h+^|;KRQ;nLB5gxkD?KaK zT1nuUVXdU%KTXV)bZXdE$$ohjdu1qd+k^B=G+1zeISa-fgT(-X!IEOP!yw5{J7chH z88$zV%ejDSu=IkOJ1WnaG1-Npmy$f$5S{55w#lUy>`KcN5^Dde65x4)9jb{e-5@a1 z^h6d)XXuG&8+D||mXc|jf7J^ZPMJz#YArN`sEiDqkjxrZLs&DUHah-(HK%$o2&Sm= z{5bT5wӮpRo+DHpCuM-)HUJR!cjlp{oIs+?vtq~4F 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

}t5&fXA-h#9{mLrvXaD2v-ZYPwCxq#FMyt@8 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 From 5380f218008c432368b995446497a4b43939151f Mon Sep 17 00:00:00 2001 From: John Lees Date: Thu, 30 Aug 2018 10:16:50 -0400 Subject: [PATCH 04/14] Update gitignore --- .gitignore | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 6 +-- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 941da20..d71f5d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,112 @@ glmnet_python/GLMnet.so + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json diff --git a/setup.py b/setup.py index 5272bc0..11a0b27 100644 --- a/setup.py +++ b/setup.py @@ -10,14 +10,14 @@ def run(self): subprocess.check_call(command, shell=True) setup(name='glmnet_python_pyseer', - version = '0.2.0', + version = '0.2.1', description = 'Python version of glmnet, from Stanford University', long_description=open('README.md').read(), url="https://github.com/johnlees/glmnet_python", author = 'Han Fang (modified by John Lees)', author_email = 'hanfang.cshl@gmail.com (john@johnlees.me)', license = 'GPL-2', - packages=['glmnet_python_pyseer'], + packages=['glmnet_python'], install_requires=['joblib>=0.10.3'], cmdclass={ 'install': CustomInstallCommand, @@ -31,5 +31,5 @@ def run(self): 'Programming Language :: Python :: 3.4', 'Operating System :: Unix', ], - keywords='glm glmnet ridge lasso elasticnet', + keywords='glm glmnet ridge lasso elasticnet' ) From c017d0431f9530dd6eea014c79115ca9692654bf Mon Sep 17 00:00:00 2001 From: John Lees Date: Thu, 30 Aug 2018 14:49:49 -0400 Subject: [PATCH 05/14] Use correct directory in setup.py install --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 11a0b27..4af9461 100644 --- a/setup.py +++ b/setup.py @@ -6,11 +6,13 @@ class CustomInstallCommand(install): """Compile shared fortran library""" def run(self): - command = ['gfortran ./glmnet_python/GLMnet.f -fPIC -fdefault-real-8 -shared -o ./glmnet_python/GLMnet.so'] + dir_path = os.path.dirname(os.path.realpath(__file__)) + command = ['gfortran ' + dir_path + '/glmnet_python/GLMnet.f -fPIC -fdefault-real-8 ' + '-shared -o ' + dir_path + '/glmnet_python/GLMnet.so'] subprocess.check_call(command, shell=True) setup(name='glmnet_python_pyseer', - version = '0.2.1', + version = '0.2.2', description = 'Python version of glmnet, from Stanford University', long_description=open('README.md').read(), url="https://github.com/johnlees/glmnet_python", From f605f9c297397302ce6447306f9b77f595685a59 Mon Sep 17 00:00:00 2001 From: John Lees Date: Tue, 11 Sep 2018 12:50:15 -0400 Subject: [PATCH 06/14] Remove fortran compilation from setup.py --- setup.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/setup.py b/setup.py index 4af9461..f17e41e 100644 --- a/setup.py +++ b/setup.py @@ -1,16 +1,7 @@ import os, sys from setuptools import setup, find_packages -from setuptools.command.install import install import subprocess -class CustomInstallCommand(install): - """Compile shared fortran library""" - def run(self): - dir_path = os.path.dirname(os.path.realpath(__file__)) - command = ['gfortran ' + dir_path + '/glmnet_python/GLMnet.f -fPIC -fdefault-real-8 ' - '-shared -o ' + dir_path + '/glmnet_python/GLMnet.so'] - subprocess.check_call(command, shell=True) - setup(name='glmnet_python_pyseer', version = '0.2.2', description = 'Python version of glmnet, from Stanford University', From b8c270e89b01af15d7aca780832bc674dc8832d5 Mon Sep 17 00:00:00 2001 From: John Lees Date: Tue, 11 Sep 2018 16:25:35 -0400 Subject: [PATCH 07/14] Corrected setup.py --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index f17e41e..f44b19d 100644 --- a/setup.py +++ b/setup.py @@ -12,9 +12,6 @@ license = 'GPL-2', packages=['glmnet_python'], install_requires=['joblib>=0.10.3'], - cmdclass={ - 'install': CustomInstallCommand, - }, classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Science/Research', From 82712c6a53cb20f4f5632661ff6bcce89e177d38 Mon Sep 17 00:00:00 2001 From: John Lees Date: Tue, 11 Sep 2018 16:26:26 -0400 Subject: [PATCH 08/14] Remove pyseer references --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f44b19d..d2eb77c 100644 --- a/setup.py +++ b/setup.py @@ -2,13 +2,13 @@ from setuptools import setup, find_packages import subprocess -setup(name='glmnet_python_pyseer', +setup(name='glmnet_python', version = '0.2.2', description = 'Python version of glmnet, from Stanford University', long_description=open('README.md').read(), url="https://github.com/johnlees/glmnet_python", author = 'Han Fang (modified by John Lees)', - author_email = 'hanfang.cshl@gmail.com (john@johnlees.me)', + author_email = 'hanfang.cshl@gmail.com,john@johnlees.me', license = 'GPL-2', packages=['glmnet_python'], install_requires=['joblib>=0.10.3'], From 863e37bbfd18cefbe470fbc14e8644eaefabc6b0 Mon Sep 17 00:00:00 2001 From: John Lees Date: Tue, 23 Oct 2018 10:21:43 -0400 Subject: [PATCH 09/14] Add .so to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index d2eb77c..c48cb2d 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ license = 'GPL-2', packages=['glmnet_python'], install_requires=['joblib>=0.10.3'], + package_data={'glmnet_python': ['*.so', 'glmnet_python/*.so']}, classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Science/Research', From 6f0ce0b6009a9066aaf4a49201ad7f806a24a4a3 Mon Sep 17 00:00:00 2001 From: Marco Galardini Date: Thu, 28 Nov 2019 06:49:02 -0500 Subject: [PATCH 10/14] Fix build by invoking gfortran directly (#1) * Fix build by invoking gfortran directly Relatively ugly hack to allow downstream packages to use the library * Clean up the build process --- setup.py | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index c48cb2d..febc562 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,41 @@ import os, sys -from setuptools import setup, find_packages +from setuptools import setup, Extension +from setuptools.command.build_ext import build_ext import subprocess +class GfortranExtension(Extension): + def __init__(self, name, sourcedir='', input='', output=''): + Extension.__init__(self, name, sources=[]) + self.sourcedir = os.path.abspath(sourcedir) + self.input = os.path.join(self.sourcedir, input) + self.output = os.path.join(self.sourcedir, output) + +class GfortranBuild(build_ext): + def run(self): + try: + out = subprocess.check_output(['gfortran', '--version']) + except OSError: + raise RuntimeError("gfortran must be installed to build the following extensions: " + + ", ".join(e.name for e in self.extensions)) + + for ext in self.extensions: + self.build_extension(ext) + + def build_extension(self, ext): + gfortran_args = ['-fPIC', + '-fdefault-real-8', + '-shared', + '-o', + ext.output] + + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) + + env = os.environ.copy() + subprocess.check_call(['gfortran', ext.input] + gfortran_args, cwd=self.build_temp, env=env) + setup(name='glmnet_python', - version = '0.2.2', + version = '1.0.0', description = 'Python version of glmnet, from Stanford University', long_description=open('README.md').read(), url="https://github.com/johnlees/glmnet_python", @@ -22,5 +54,8 @@ 'Programming Language :: Python :: 3.4', 'Operating System :: Unix', ], - keywords='glm glmnet ridge lasso elasticnet' - ) + keywords='glm glmnet ridge lasso elasticnet', + ext_modules=[GfortranExtension('GLMnet', 'glmnet_python', + 'GLMnet.f', 'GLMnet.so')], + cmdclass={'build_ext': GfortranBuild}, +) From ce255d8965eddc456b1d014b1dd5e80a68aae0f7 Mon Sep 17 00:00:00 2001 From: John Lees Date: Mon, 9 Mar 2020 16:42:59 +0000 Subject: [PATCH 11/14] Convert scipy to numpy functions Fix pylint errors throughout --- glmnet_python/coxnet.py | 64 ++++++++--------- glmnet_python/cvcompute.py | 16 ++--- glmnet_python/cvelnet.py | 18 ++--- glmnet_python/cvfishnet.py | 24 +++---- glmnet_python/cvglmnet.py | 72 +++++++++---------- glmnet_python/cvglmnetCoef.py | 4 +- glmnet_python/cvglmnetPlot.py | 20 +++--- glmnet_python/cvglmnetPredict.py | 10 +-- glmnet_python/cvlognet.py | 88 +++++++++++------------ glmnet_python/cvmrelnet.py | 20 +++--- glmnet_python/cvmultnet.py | 54 +++++++------- glmnet_python/elnet.py | 66 ++++++++--------- glmnet_python/fishnet.py | 66 ++++++++--------- glmnet_python/glmnet.py | 111 +++++++++++++++-------------- glmnet_python/glmnetCoef.py | 14 ++-- glmnet_python/glmnetControl.py | 24 +++---- glmnet_python/glmnetPlot.py | 36 +++++----- glmnet_python/glmnetPredict.py | 117 +++++++++++++++--------------- glmnet_python/glmnetSet.py | 60 ++++++++-------- glmnet_python/lognet.py | 118 +++++++++++++++---------------- glmnet_python/mrelnet.py | 98 ++++++++++++------------- glmnet_python/wtmean.py | 10 +-- 22 files changed, 556 insertions(+), 554 deletions(-) diff --git a/glmnet_python/coxnet.py b/glmnet_python/coxnet.py index 30fb6e1..3388f2c 100644 --- a/glmnet_python/coxnet.py +++ b/glmnet_python/coxnet.py @@ -6,7 +6,7 @@ status -- column 1 """ # import packages/methods -import scipy +import numpy import ctypes from loadGlmLib import loadGlmLib @@ -20,7 +20,7 @@ def coxnet(x, is_sparse, irs, pcs, y, weights, offset, parm, # pre-process data ty = y[:, 0] tevent = y[:, 1] - if scipy.any(ty <= 0): + if numpy.any(ty <= 0): raise ValueError('negative event time not permitted for cox family') if len(offset) == 0: offset = ty*0 @@ -35,17 +35,17 @@ def coxnet(x, is_sparse, irs, pcs, y, weights, offset, parm, ###################################### # force inputs into fortran order and scipy float64 copyFlag = False - x = x.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - irs = irs.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - pcs = pcs.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - ty = ty.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - tevent = tevent.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - offset = offset.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - weights = weights.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - jd = jd.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - vp = vp.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - cl = cl.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - ulam = ulam.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) + x = x.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + irs = irs.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + pcs = pcs.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + ty = ty.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + tevent = tevent.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + offset = offset.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + weights = weights.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + jd = jd.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + vp = vp.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + cl = cl.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + ulam = ulam.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) ###################################### # --------- ALLOCATE OUTPUTS --------- @@ -54,24 +54,24 @@ def coxnet(x, is_sparse, irs, pcs, y, weights, offset, parm, lmu = -1 lmu_r = ctypes.c_int(lmu) # ca - ca = scipy.zeros([nx, nlam], dtype = scipy.float64) - ca = ca.astype(dtype = scipy.float64, order = 'F', copy = False) + ca = numpy.zeros([nx, nlam], dtype = numpy.float64) + ca = ca.astype(dtype = numpy.float64, order = 'F', copy = False) ca_r = ca.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # ia - ia = -1*scipy.ones([nx], dtype = scipy.int32) - ia = ia.astype(dtype = scipy.int32, order = 'F', copy = False) + ia = -1*numpy.ones([nx], dtype = numpy.int32) + ia = ia.astype(dtype = numpy.int32, order = 'F', copy = False) ia_r = ia.ctypes.data_as(ctypes.POINTER(ctypes.c_int)) # nin - nin = -1*scipy.ones([nlam], dtype = scipy.int32) - nin = nin.astype(dtype = scipy.int32, order = 'F', copy = False) + nin = -1*numpy.ones([nlam], dtype = numpy.int32) + nin = nin.astype(dtype = numpy.int32, order = 'F', copy = False) nin_r = nin.ctypes.data_as(ctypes.POINTER(ctypes.c_int)) # dev - dev = -1*scipy.ones([nlam], dtype = scipy.float64) - dev = dev.astype(dtype = scipy.float64, order = 'F', copy = False) + dev = -1*numpy.ones([nlam], dtype = numpy.float64) + dev = dev.astype(dtype = numpy.float64, order = 'F', copy = False) dev_r = dev.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # alm - alm = -1*scipy.ones([nlam], dtype = scipy.float64) - alm = alm.astype(dtype = scipy.float64, order = 'F', copy = False) + alm = -1*numpy.ones([nlam], dtype = numpy.float64) + alm = alm.astype(dtype = numpy.float64, order = 'F', copy = False) alm_r = alm.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # nlp nlp = -1 @@ -146,21 +146,21 @@ def coxnet(x, is_sparse, irs, pcs, y, weights, offset, parm, ninmax = max(nin) # fix first value of alm (from inf to correct value) if ulam[0] == 0.0: - t1 = scipy.log(alm[1]) - t2 = scipy.log(alm[2]) - alm[0] = scipy.exp(2*t1 - t2) + t1 = numpy.log(alm[1]) + t2 = numpy.log(alm[2]) + alm[0] = numpy.exp(2*t1 - t2) # create return fit dictionary if ninmax > 0: ca = ca[0:ninmax, :] - df = scipy.sum(scipy.absolute(ca) > 0, axis=0) + df = numpy.sum(numpy.absolute(ca) > 0, axis=0) ja = ia[0:ninmax] - 1 # ia is 1-indexed in fortran - oja = scipy.argsort(ja) + oja = numpy.argsort(ja) ja1 = ja[oja] - beta = scipy.zeros([nvars, lmu], dtype = scipy.float64) + beta = numpy.zeros([nvars, lmu], dtype = numpy.float64) beta[ja1, :] = ca[oja, :] else: - beta = scipy.zeros([nvars, lmu], dtype = scipy.float64) - df = scipy.zeros([1, lmu], dtype = scipy.float64) + beta = numpy.zeros([nvars, lmu], dtype = numpy.float64) + df = numpy.zeros([1, lmu], dtype = numpy.float64) fit = dict() fit['beta'] = beta @@ -170,7 +170,7 @@ def coxnet(x, is_sparse, irs, pcs, y, weights, offset, parm, fit['lambdau'] = alm fit['npasses'] = nlp_r.value fit['jerr'] = jerr_r.value - fit['dim'] = scipy.array([nvars, lmu], dtype = scipy.integer) + fit['dim'] = numpy.array([nvars, lmu], dtype = numpy.integer) fit['offset'] = is_offset fit['class'] = 'coxnet' diff --git a/glmnet_python/cvcompute.py b/glmnet_python/cvcompute.py index 7dcac66..7d96dea 100644 --- a/glmnet_python/cvcompute.py +++ b/glmnet_python/cvcompute.py @@ -4,24 +4,24 @@ Compute the weighted mean and SD within folds, and hence the SE of the mean """ -import scipy +import numpy from wtmean import wtmean def cvcompute(mat, weights, foldid, nlams): if len(weights.shape) > 1: - weights = scipy.reshape(weights, [weights.shape[0], ]) - wisum = scipy.bincount(foldid, weights = weights) - nfolds = scipy.amax(foldid) + 1 - outmat = scipy.ones([nfolds, mat.shape[1]])*scipy.NaN - good = scipy.zeros([nfolds, mat.shape[1]]) - mat[scipy.isinf(mat)] = scipy.NaN + weights = numpy.reshape(weights, [weights.shape[0], ]) + wisum = numpy.bincount(foldid, weights = weights) + nfolds = numpy.amax(foldid) + 1 + outmat = numpy.ones([nfolds, mat.shape[1]])*numpy.NaN + good = numpy.zeros([nfolds, mat.shape[1]]) + mat[numpy.isinf(mat)] = numpy.NaN for i in range(nfolds): tf = foldid == i mati = mat[tf, ] wi = weights[tf, ] outmat[i, :] = wtmean(mati, wi) good[i, 0:nlams[i]] = 1 - N = scipy.sum(good, axis = 0) + N = numpy.sum(good, axis = 0) cvcpt = dict() cvcpt['cvraw'] = outmat cvcpt['weights'] = wisum diff --git a/glmnet_python/cvelnet.py b/glmnet_python/cvelnet.py index e9f6349..ac89c38 100644 --- a/glmnet_python/cvelnet.py +++ b/glmnet_python/cvelnet.py @@ -3,7 +3,7 @@ Internal cvglmnet function. See also cvglmnet. """ -import scipy +import numpy from glmnetPredict import glmnetPredict from wtmean import wtmean from cvcompute import cvcompute @@ -31,29 +31,29 @@ def cvelnet(fit, \ if len(offset) > 0: y = y - offset - predmat = scipy.ones([y.size, lambdau.size])*scipy.NAN - nfolds = scipy.amax(foldid) + 1 + predmat = numpy.ones([y.size, lambdau.size])*numpy.NAN + nfolds = numpy.amax(foldid) + 1 nlams = [] for i in range(nfolds): which = foldid == i fitobj = fit[i].copy() fitobj['offset'] = False preds = glmnetPredict(fitobj, x[which, ]) - nlami = scipy.size(fit[i]['lambdau']) + nlami = numpy.size(fit[i]['lambdau']) predmat[which, 0:nlami] = preds nlams.append(nlami) # convert nlams to scipy array - nlams = scipy.array(nlams, dtype = scipy.integer) + nlams = numpy.array(nlams, dtype = numpy.integer) - N = y.shape[0] - scipy.sum(scipy.isnan(predmat), axis = 0) - yy = scipy.tile(y, [1, lambdau.size]) + N = y.shape[0] - numpy.sum(numpy.isnan(predmat), axis = 0) + yy = numpy.tile(y, [1, lambdau.size]) if ptype == 'mse': cvraw = (yy - predmat)**2 elif ptype == 'deviance': cvraw = (yy - predmat)**2 elif ptype == 'mae': - cvraw = scipy.absolute(yy - predmat) + cvraw = numpy.absolute(yy - predmat) if y.size/nfolds < 3 and grouped == True: print('Option grouped=false enforced in cv.glmnet, since < 3 observations per fold') @@ -67,7 +67,7 @@ def cvelnet(fit, \ cvm = wtmean(cvraw, weights) sqccv = (cvraw - cvm)**2 - cvsd = scipy.sqrt(wtmean(sqccv, weights)/(N-1)) + cvsd = numpy.sqrt(wtmean(sqccv, weights)/(N-1)) result = dict() result['cvm'] = cvm diff --git a/glmnet_python/cvfishnet.py b/glmnet_python/cvfishnet.py index 6dbd3bd..f97b329 100644 --- a/glmnet_python/cvfishnet.py +++ b/glmnet_python/cvfishnet.py @@ -3,7 +3,7 @@ Internal cvglmnet function. See also cvglmnet. """ -import scipy +import numpy from glmnetPredict import glmnetPredict from wtmean import wtmean from cvcompute import cvcompute @@ -34,8 +34,8 @@ def cvfishnet(fit, \ else: is_offset = False - predmat = scipy.ones([y.size, lambdau.size])*scipy.NAN - nfolds = scipy.amax(foldid) + 1 + predmat = numpy.ones([y.size, lambdau.size])*numpy.NAN + nfolds = numpy.amax(foldid) + 1 nlams = [] for i in range(nfolds): which = foldid == i @@ -43,23 +43,23 @@ def cvfishnet(fit, \ if is_offset: off_sub = offset[which] else: - off_sub = scipy.empty([0]) + off_sub = numpy.empty([0]) preds = glmnetPredict(fitobj, x[which, ], offset = off_sub) - nlami = scipy.size(fit[i]['lambdau']) + nlami = numpy.size(fit[i]['lambdau']) predmat[which, 0:nlami] = preds nlams.append(nlami) # convert nlams to scipy array - nlams = scipy.array(nlams, dtype = scipy.integer) + nlams = numpy.array(nlams, dtype = numpy.integer) - N = y.shape[0] - scipy.sum(scipy.isnan(predmat), axis = 0) - yy = scipy.tile(y, [1, lambdau.size]) + N = y.shape[0] - numpy.sum(numpy.isnan(predmat), axis = 0) + yy = numpy.tile(y, [1, lambdau.size]) if ptype == 'mse': cvraw = (yy - predmat)**2 elif ptype == 'deviance': cvraw = devi(yy, predmat) elif ptype == 'mae': - cvraw = scipy.absolute(yy - predmat) + cvraw = numpy.absolute(yy - predmat) if y.size/nfolds < 3 and grouped == True: print('Option grouped=false enforced in cvglmnet, since < 3 observations per fold') @@ -73,7 +73,7 @@ def cvfishnet(fit, \ cvm = wtmean(cvraw, weights) sqccv = (cvraw - cvm)**2 - cvsd = scipy.sqrt(wtmean(sqccv, weights)/(N-1)) + cvsd = numpy.sqrt(wtmean(sqccv, weights)/(N-1)) result = dict() result['cvm'] = cvm @@ -88,8 +88,8 @@ def cvfishnet(fit, \ # end of cvfishnet #========================= def devi(yy, eta): - deveta = yy*eta - scipy.exp(eta) - devy = yy*scipy.log(yy) - yy + deveta = yy*eta - numpy.exp(eta) + devy = yy*numpy.log(yy) - yy devy[yy == 0] = 0 result = 2*(devy - deveta) return(result) diff --git a/glmnet_python/cvglmnet.py b/glmnet_python/cvglmnet.py index a539d54..e1a07a6 100644 --- a/glmnet_python/cvglmnet.py +++ b/glmnet_python/cvglmnet.py @@ -126,8 +126,8 @@ class Type of regression - internal usage. EXAMPLES: # Gaussian - x = scipy.random.rand(100, 10) - y = scipy.random.rand(100, 1) + x = numpy.random.rand(100, 10) + y = numpy.random.rand(100, 1) cvfit = cvglmnet(x = x, y = y) cvglmnetPlot(cvfit) print( cvglmnetCoef(cvfit) ) @@ -136,27 +136,27 @@ class Type of regression - internal usage. cvglmnetPlot(cvfit1) # Binomial - x = scipy.random.rand(100, 10) - y = scipy.random.rand(100,1) + x = numpy.random.rand(100, 10) + y = numpy.random.rand(100,1) y = (y > 0.5)*1.0 fit = cvglmnet(x = x, y = y, family = 'binomial', ptype = 'class') cvglmnetPlot(fit) # poisson - x = scipy.random.rand(100,10) - y = scipy.random.poisson(size = [100, 1])*1.0 + x = numpy.random.rand(100,10) + y = numpy.random.poisson(size = [100, 1])*1.0 cvfit = cvglmnet(x = x, y = y, family = 'poisson') cvglmnetPlot(cvfit) # Multivariate Gaussian: - x = scipy.random.rand(100, 10) - y = scipy.random.rand(100,3) + x = numpy.random.rand(100, 10) + y = numpy.random.rand(100,3) cvfit = cvglmnet(x = x, y = y, family = 'mgaussian') cvglmnetPlot(cvfit) # Multinomial - x = scipy.random.rand(100,10) - y = scipy.random.rand(100,1) + x = numpy.random.rand(100,10) + y = numpy.random.rand(100,1) y[y < 0.3] = 1.0 y[y < 0.6] = 2.0 y[y < 1.0] = 3.0 @@ -199,7 +199,7 @@ class Type of regression - internal usage. import multiprocessing from glmnetSet import glmnetSet from glmnetPredict import glmnetPredict -import scipy +import numpy from glmnet import glmnet from cvelnet import cvelnet from cvlognet import cvlognet @@ -212,7 +212,7 @@ def cvglmnet(*, x, family = 'gaussian', ptype = 'default', nfolds = 10, - foldid = scipy.empty([0]), + foldid = numpy.empty([0]), parallel = 1, keep = False, grouped = True, @@ -227,18 +227,18 @@ def cvglmnet(*, x, # we should not really need this. user must supply the right shape # if y.shape[0] != nobs: - # y = scipy.transpose(y) + # y = numpy.transpose(y) # convert 1d python array of size nobs to 2d python array of size nobs x 1 if len(y.shape) == 1: - y = scipy.reshape(y, [y.size, 1]) + y = numpy.reshape(y, [y.size, 1]) # we should not really need this. user must supply the right shape # if (len(options['offset']) > 0) and (options['offset'].shape[0] != nobs): - # options['offset'] = scipy.transpose(options['offset']) + # options['offset'] = numpy.transpose(options['offset']) if len(options['weights']) == 0: - options['weights'] = scipy.ones([nobs, 1], dtype = scipy.float64) + options['weights'] = numpy.ones([nobs, 1], dtype = numpy.float64) # main call to glmnet glmfit = glmnet(x = x, y = y, family = family, **options) @@ -246,34 +246,34 @@ def cvglmnet(*, x, is_offset = glmfit['offset'] options['lambdau'] = glmfit['lambdau'] - nz = glmnetPredict(glmfit, scipy.empty([0]), scipy.empty([0]), 'nonzero') + nz = glmnetPredict(glmfit, numpy.empty([0]), numpy.empty([0]), 'nonzero') if glmfit['class'] == 'multnet': - nnz = scipy.zeros([len(options['lambdau']), len(nz)]) + nnz = numpy.zeros([len(options['lambdau']), len(nz)]) for i in range(len(nz)): - nnz[:, i] = scipy.transpose(scipy.sum(nz[i], axis = 0)) - nz = scipy.ceil(scipy.median(nnz, axis = 1)) + nnz[:, i] = numpy.transpose(numpy.sum(nz[i], axis = 0)) + nz = numpy.ceil(numpy.median(nnz, axis = 1)) elif glmfit['class'] == 'mrelnet': - nz = scipy.transpose(scipy.sum(nz[0], axis = 0)) + nz = numpy.transpose(numpy.sum(nz[0], axis = 0)) else: - nz = scipy.transpose(scipy.sum(nz, axis = 0)) + nz = numpy.transpose(numpy.sum(nz, axis = 0)) if len(foldid) == 0: - ma = scipy.tile(scipy.arange(nfolds), [1, int(scipy.floor(nobs/nfolds))]) - mb = scipy.arange(scipy.mod(nobs, nfolds)) - mb = scipy.reshape(mb, [1, mb.size]) - population = scipy.append(ma, mb, axis = 1) - mc = scipy.random.permutation(len(population)) + ma = numpy.tile(numpy.arange(nfolds), [1, int(numpy.floor(nobs/nfolds))]) + mb = numpy.arange(numpy.mod(nobs, nfolds)) + mb = numpy.reshape(mb, [1, mb.size]) + population = numpy.append(ma, mb, axis = 1) + mc = numpy.random.permutation(len(population)) mc = mc[0:nobs] foldid = population[mc] - foldid = scipy.reshape(foldid, [foldid.size,]) + foldid = numpy.reshape(foldid, [foldid.size,]) else: - nfolds = scipy.amax(foldid) + 1 + nfolds = numpy.amax(foldid) + 1 if nfolds < 3: raise ValueError('nfolds must be bigger than 3; nfolds = 10 recommended') cpredmat = list() - foldid = scipy.reshape(foldid, [foldid.size, ]) + foldid = numpy.reshape(foldid, [foldid.size, ]) if parallel != 1: if parallel == -1: num_cores = multiprocessing.cpu_count() @@ -318,10 +318,10 @@ def cvglmnet(*, x, CVerr = dict() CVerr['lambdau'] = options['lambdau'] - CVerr['cvm'] = scipy.transpose(cvm) - CVerr['cvsd'] = scipy.transpose(cvsd) - CVerr['cvup'] = scipy.transpose(cvm + cvsd) - CVerr['cvlo'] = scipy.transpose(cvm - cvsd) + CVerr['cvm'] = numpy.transpose(cvm) + CVerr['cvsd'] = numpy.transpose(cvsd) + CVerr['cvup'] = numpy.transpose(cvm + cvsd) + CVerr['cvlo'] = numpy.transpose(cvm - cvsd) CVerr['nzero'] = nz CVerr['name'] = cvname CVerr['glmnet_fit'] = glmfit @@ -330,10 +330,10 @@ def cvglmnet(*, x, CVerr['foldid'] = foldid if ptype == 'auc': cvm = -cvm - CVerr['lambda_min'] = scipy.amax(options['lambdau'][cvm <= scipy.amin(cvm)]).reshape([1]) + CVerr['lambda_min'] = numpy.amax(options['lambdau'][cvm <= numpy.amin(cvm)]).reshape([1]) idmin = options['lambdau'] == CVerr['lambda_min'] semin = cvm[idmin] + cvsd[idmin] - CVerr['lambda_1se'] = scipy.amax(options['lambdau'][cvm <= semin]).reshape([1]) + CVerr['lambda_1se'] = numpy.amax(options['lambdau'][cvm <= semin]).reshape([1]) CVerr['class'] = 'cvglmnet' return(CVerr) diff --git a/glmnet_python/cvglmnetCoef.py b/glmnet_python/cvglmnetCoef.py index e0f0adf..210c6f7 100644 --- a/glmnet_python/cvglmnetCoef.py +++ b/glmnet_python/cvglmnetCoef.py @@ -69,7 +69,7 @@ """ -import scipy +import numpy from glmnetCoef import glmnetCoef def cvglmnetCoef(obj, s = None): @@ -77,7 +77,7 @@ def cvglmnetCoef(obj, s = None): if s is None or len(s) == 0: s = obj['lambda_1se'] - if isinstance(s, scipy.ndarray): + if isinstance(s, numpy.ndarray): lambdau = s elif isinstance(s, str): sbase = ['lambda_1se', 'lambda_min'] diff --git a/glmnet_python/cvglmnetPlot.py b/glmnet_python/cvglmnetPlot.py index f55a97d..30117aa 100644 --- a/glmnet_python/cvglmnetPlot.py +++ b/glmnet_python/cvglmnetPlot.py @@ -44,11 +44,11 @@ EXAMPLES: - scipy.random.seed(1) - x=scipy.random.normal(size = (100,20)) - y=scipy.random.normal(size = (100,1)) - g2=scipy.random.choice(2,size = (100,1))*1.0 - g4=scipy.random.choice(4,size = (100,1))*1.0 + numpy.random.seed(1) + x=numpy.random.normal(size = (100,20)) + y=numpy.random.normal(size = (100,1)) + g2=numpy.random.choice(2,size = (100,1))*1.0 + g4=numpy.random.choice(4,size = (100,1))*1.0 plt.figure() fit1=cvglmnet(x = x.copy(),y = y.copy()) @@ -64,13 +64,13 @@ """ -import scipy +import numpy def cvglmnetPlot(cvobject, sign_lambda = 1.0, **options): import matplotlib.pyplot as plt - sloglam = sign_lambda*scipy.log(cvobject['lambdau']) + sloglam = sign_lambda*numpy.log(cvobject['lambdau']) fig = plt.gcf() ax1 = plt.gca() @@ -87,12 +87,12 @@ def cvglmnetPlot(cvobject, sign_lambda = 1.0, **options): xlim1 = ax1.get_xlim() ylim1 = ax1.get_ylim() - xval = sign_lambda*scipy.log(scipy.array([cvobject['lambda_min'], cvobject['lambda_min']])) + xval = sign_lambda*numpy.log(numpy.array([cvobject['lambda_min'], cvobject['lambda_min']])) plt.plot(xval, ylim1, color = 'b', linestyle = 'dashed', \ linewidth = 1) if cvobject['lambda_min'] != cvobject['lambda_1se']: - xval = sign_lambda*scipy.log([cvobject['lambda_1se'], cvobject['lambda_1se']]) + xval = sign_lambda*numpy.log([cvobject['lambda_1se'], cvobject['lambda_1se']]) plt.plot(xval, ylim1, color = 'b', linestyle = 'dashed', \ linewidth = 1) @@ -100,7 +100,7 @@ def cvglmnetPlot(cvobject, sign_lambda = 1.0, **options): ax2.xaxis.tick_top() atdf = ax1.get_xticks() - indat = scipy.ones(atdf.shape, dtype = scipy.integer) + indat = numpy.ones(atdf.shape, dtype = numpy.integer) if sloglam[-1] >= sloglam[1]: for j in range(len(sloglam)-1, -1, -1): indat[atdf <= sloglam[j]] = j diff --git a/glmnet_python/cvglmnetPredict.py b/glmnet_python/cvglmnetPredict.py index 7d325c5..6f749e7 100644 --- a/glmnet_python/cvglmnetPredict.py +++ b/glmnet_python/cvglmnetPredict.py @@ -67,23 +67,23 @@ cvglmnet and glmnetPredict. EXAMPLES: - x = scipy.random.rand(100, 10) - y = scipy.random.rand(100, 1) + x = numpy.random.rand(100, 10) + y = numpy.random.rand(100, 1) cvfit = cvglmnet(x = x, y = y) cvglmnetPredict(cvfit, x[0:5, :], 'lambda_min') - cvglmnetPredict(cvfit, x[0:5, :], scipy.array([0.0866, 0.2323])) + cvglmnetPredict(cvfit, x[0:5, :], numpy.array([0.0866, 0.2323])) """ from cvglmnetCoef import cvglmnetCoef from glmnetPredict import glmnetPredict -import scipy +import numpy def cvglmnetPredict(obj, newx = None, s = 'lambda_1se', **options): if newx is None: CVpred = cvglmnetCoef(obj) return(CVpred) - if type(s) == scipy.ndarray and s.dtype == 'float64': + if type(s) == numpy.ndarray and s.dtype == 'float64': lambdau = s elif s in ['lambda_1se', 'lambda_min']: lambdau = obj[s] diff --git a/glmnet_python/cvlognet.py b/glmnet_python/cvlognet.py index adae68c..2915fd4 100644 --- a/glmnet_python/cvlognet.py +++ b/glmnet_python/cvlognet.py @@ -3,7 +3,7 @@ Internal function called by cvglmnet. See also cvglmnet """ -import scipy +import numpy from glmnetPredict import glmnetPredict from wtmean import wtmean from cvcompute import cvcompute @@ -33,15 +33,15 @@ def cvlognet(fit, \ prob_max = 1 - prob_min nc = y.shape[1] if nc == 1: - classes, sy = scipy.unique(y, return_inverse = True) + classes, sy = numpy.unique(y, return_inverse = True) nc = len(classes) - indexes = scipy.eye(nc, nc) + indexes = numpy.eye(nc, nc) y = indexes[sy, :] else: - classes = scipy.arange(nc) + 1 # 1:nc + classes = numpy.arange(nc) + 1 # 1:nc N = y.size - nfolds = scipy.amax(foldid) + 1 + nfolds = numpy.amax(foldid) + 1 if (N/nfolds < 10) and (type == 'auc'): print('Warning: Too few (<10) observations per fold for type.measure=auc in cvlognet') print('Warning: changed to type.measure = deviance. Alternately, use smaller value ') @@ -53,8 +53,8 @@ def cvlognet(fit, \ grouped = False is_offset = not(len(offset) == 0) - predmat = scipy.ones([y.shape[0], lambdau.size])*scipy.NAN - nfolds = scipy.amax(foldid) + 1 + predmat = numpy.ones([y.shape[0], lambdau.size])*numpy.NAN + nfolds = numpy.amax(foldid) + 1 nlams = [] for i in range(nfolds): which = foldid == i @@ -62,46 +62,46 @@ def cvlognet(fit, \ if is_offset: off_sub = offset[which, ] else: - off_sub = scipy.empty([0]) - preds = glmnetPredict(fitobj, x[which, ], scipy.empty([0]), 'response', False, off_sub) - nlami = scipy.size(fit[i]['lambdau']) + off_sub = numpy.empty([0]) + preds = glmnetPredict(fitobj, x[which, ], numpy.empty([0]), 'response', False, off_sub) + nlami = numpy.size(fit[i]['lambdau']) predmat[which, 0:nlami] = preds nlams.append(nlami) # convert nlams to scipy array - nlams = scipy.array(nlams, dtype = scipy.integer) + nlams = numpy.array(nlams, dtype = numpy.integer) if ptype == 'auc': - cvraw = scipy.zeros([nfolds, lambdau.size])*scipy.NaN - good = scipy.zeros([nfolds, lambdau.size]) + cvraw = numpy.zeros([nfolds, lambdau.size])*numpy.NaN + good = numpy.zeros([nfolds, lambdau.size]) for i in range(nfolds): good[i, 0:nlams[i]] = 1 which = foldid == i for j in range(nlams[i]): cvraw[i,j] = auc_mat(y[which,], predmat[which,j], weights[which]) - N = scipy.sum(good, axis = 0) - sweights = scipy.zeros([nfolds, 1]) + N = numpy.sum(good, axis = 0) + sweights = numpy.zeros([nfolds, 1]) for i in range(nfolds): - sweights[i]= scipy.sum(weights[foldid == i], axis = 0) + sweights[i]= numpy.sum(weights[foldid == i], axis = 0) weights = sweights else: - ywt = scipy.sum(y, axis = 1, keepdims = True) - y = y/scipy.tile(ywt, [1, y.shape[1]]) + ywt = numpy.sum(y, axis = 1, keepdims = True) + y = y/numpy.tile(ywt, [1, y.shape[1]]) weights = weights*ywt - N = y.shape[0] - scipy.sum(scipy.isnan(predmat), axis = 0, keepdims = True) - yy1 = scipy.tile(y[:,0:1], [1, lambdau.size]) - yy2 = scipy.tile(y[:,1:2], [1, lambdau.size]) + N = y.shape[0] - numpy.sum(numpy.isnan(predmat), axis = 0, keepdims = True) + yy1 = numpy.tile(y[:,0:1], [1, lambdau.size]) + yy2 = numpy.tile(y[:,1:2], [1, lambdau.size]) if ptype == 'mse': cvraw = (yy1 - (1 - predmat))**2 + (yy2 - (1 - predmat))**2 elif ptype == 'deviance': - predmat = scipy.minimum(scipy.maximum(predmat, prob_min), prob_max) - lp = yy1*scipy.log(1-predmat) + yy2*scipy.log(predmat) - ly = scipy.log(y) + predmat = numpy.minimum(numpy.maximum(predmat, prob_min), prob_max) + lp = yy1*numpy.log(1-predmat) + yy2*numpy.log(predmat) + ly = numpy.log(y) ly[y == 0] = 0 - ly = scipy.dot(y*ly, scipy.array([1.0, 1.0]).reshape([2,1])) - cvraw = 2*(scipy.tile(ly, [1, lambdau.size]) - lp) + ly = numpy.dot(y*ly, numpy.array([1.0, 1.0]).reshape([2,1])) + cvraw = 2*(numpy.tile(ly, [1, lambdau.size]) - lp) elif ptype == 'mae': - cvraw = scipy.absolute(yy1 - (1 - predmat)) + scipy.absolute(yy2 - (1 - predmat)) + cvraw = numpy.absolute(yy1 - (1 - predmat)) + numpy.absolute(yy2 - (1 - predmat)) elif ptype == 'class': cvraw = yy1*(predmat > 0.5) + yy2*(predmat <= 0.5) @@ -117,7 +117,7 @@ def cvlognet(fit, \ cvm = wtmean(cvraw, weights) sqccv = (cvraw - cvm)**2 - cvsd = scipy.sqrt(wtmean(sqccv, weights)/(N-1)) + cvsd = numpy.sqrt(wtmean(sqccv, weights)/(N-1)) result = dict() result['cvm'] = cvm @@ -137,37 +137,37 @@ def cvlognet(fit, \ #========================= def auc_mat(y, prob, weights = None): if weights == None or len(weights) == 0: - weights = scipy.ones([y.shape[0], 1]) + weights = numpy.ones([y.shape[0], 1]) wweights = weights*y wweights = wweights.flatten() - wweights = scipy.reshape(wweights, [1, wweights.size]) + wweights = numpy.reshape(wweights, [1, wweights.size]) ny= y.shape[0] - a = scipy.zeros([ny, 1]) - b = scipy.ones([ny, 1]) - yy = scipy.vstack((a, b)) - pprob = scipy.vstack((prob,prob)) + a = numpy.zeros([ny, 1]) + b = numpy.ones([ny, 1]) + yy = numpy.vstack((a, b)) + pprob = numpy.vstack((prob,prob)) result = auc(yy, pprob, wweights) return(result) #========================= def auc(y, prob, w): if len(w) == 0: - mindiff = scipy.amin(scipy.diff(scipy.unique(prob))) - pert = scipy.random.uniform(0, mindiff/3, prob.size) - t, rprob = scipy.unique(prob + pert, return_inverse = True) - n1 = scipy.sum(y, keepdims = True) + mindiff = numpy.amin(numpy.diff(numpy.unique(prob))) + pert = numpy.random.uniform(0, mindiff/3, prob.size) + t, rprob = numpy.unique(prob + pert, return_inverse = True) + n1 = numpy.sum(y, keepdims = True) n0 = y.shape[0] - n1 - u = scipy.sum(rprob[y == 1]) - n1*(n1 + 1)/2 + u = numpy.sum(rprob[y == 1]) - n1*(n1 + 1)/2 result = u/(n1*n0) else: - op = scipy.argsort(prob) + op = numpy.argsort(prob) y = y[op] w = w[op] - cw = scipy.cumsum(w) + cw = numpy.cumsum(w) w1 = w[y == 1] - cw1 = scipy.cumsum(w1) - wauc = scipy.sum(w1*(cw[y == 1] - cw1)) + cw1 = numpy.cumsum(w1) + wauc = numpy.sum(w1*(cw[y == 1] - cw1)) sumw = cw1[-1] - sumw = sumw*(c1[-1] - sumw) + sumw = sumw*(cw1[-1] - sumw) result = wauc/sumw return(result) #========================= diff --git a/glmnet_python/cvmrelnet.py b/glmnet_python/cvmrelnet.py index 40e8540..fe8a088 100644 --- a/glmnet_python/cvmrelnet.py +++ b/glmnet_python/cvmrelnet.py @@ -3,7 +3,7 @@ Internal function called by cvglmnet. See also cvglmnet """ -import scipy +import numpy from glmnetPredict import glmnetPredict from wtmean import wtmean from cvcompute import cvcompute @@ -34,27 +34,27 @@ def cvmrelnet(fit, \ if len(offset) > 0: y = y - offset - predmat = scipy.ones([nobs, nc, lambdau.size])*scipy.NAN - nfolds = scipy.amax(foldid) + 1 + predmat = numpy.ones([nobs, nc, lambdau.size])*numpy.NAN + nfolds = numpy.amax(foldid) + 1 nlams = [] for i in range(nfolds): which = foldid == i fitobj = fit[i].copy() fitobj['offset'] = False preds = glmnetPredict(fitobj, x[which, ]) - nlami = scipy.size(fit[i]['lambdau']) + nlami = numpy.size(fit[i]['lambdau']) predmat[which, 0:nlami] = preds nlams.append(nlami) # convert nlams to scipy array - nlams = scipy.array(nlams, dtype = scipy.integer) + nlams = numpy.array(nlams, dtype = numpy.integer) - N = nobs - scipy.reshape(scipy.sum(scipy.isnan(predmat[:, 1, :]), axis = 0), (1, -1)) - bigY = scipy.tile(y[:, :, None], [1, 1, lambdau.size]) + N = nobs - numpy.reshape(numpy.sum(numpy.isnan(predmat[:, 1, :]), axis = 0), (1, -1)) + bigY = numpy.tile(y[:, :, None], [1, 1, lambdau.size]) if ptype == 'mse': - cvraw = scipy.sum((bigY - predmat)**2, axis = 1).squeeze() + cvraw = numpy.sum((bigY - predmat)**2, axis = 1).squeeze() elif ptype == 'mae': - cvraw = scipy.sum(scipy.absolute(bigY - predmat), axis = 1).squeeze() + cvraw = numpy.sum(numpy.absolute(bigY - predmat), axis = 1).squeeze() if y.size/nfolds < 3 and grouped == True: print('Option grouped=false enforced in cv.glmnet, since < 3 observations per fold') @@ -68,7 +68,7 @@ def cvmrelnet(fit, \ cvm = wtmean(cvraw, weights) sqccv = (cvraw - cvm)**2 - cvsd = scipy.sqrt(wtmean(sqccv, weights)/(N-1)) + cvsd = numpy.sqrt(wtmean(sqccv, weights)/(N-1)) result = dict() result['cvm'] = cvm diff --git a/glmnet_python/cvmultnet.py b/glmnet_python/cvmultnet.py index aa3efed..9e5c2f9 100644 --- a/glmnet_python/cvmultnet.py +++ b/glmnet_python/cvmultnet.py @@ -3,7 +3,7 @@ Internal function called by cvglmnet. See also cvglmnet """ -import scipy +import numpy from glmnetPredict import glmnetPredict from wtmean import wtmean from cvcompute import cvcompute @@ -33,16 +33,16 @@ def cvmultnet(fit, \ prob_max = 1 - prob_min nc = y.shape if nc[1] == 1: - classes, sy = scipy.unique(y, return_inverse = True) + classes, sy = numpy.unique(y, return_inverse = True) nc = len(classes) - indexes = scipy.eye(nc, nc) + indexes = numpy.eye(nc, nc) y = indexes[sy, :] else: nc = nc[1] is_offset = not(len(offset) == 0) - predmat = scipy.ones([y.shape[0], nc, lambdau.size])*scipy.NAN - nfolds = scipy.amax(foldid) + 1 + predmat = numpy.ones([y.shape[0], nc, lambdau.size])*numpy.NAN + nfolds = numpy.amax(foldid) + 1 nlams = [] for i in range(nfolds): which = foldid == i @@ -50,39 +50,39 @@ def cvmultnet(fit, \ if is_offset: off_sub = offset[which, ] else: - off_sub = scipy.empty([0]) - preds = glmnetPredict(fitobj, x[which, ], scipy.empty([0]), 'response', False, off_sub) - nlami = scipy.size(fit[i]['lambdau']) + off_sub = numpy.empty([0]) + preds = glmnetPredict(fitobj, x[which, ], numpy.empty([0]), 'response', False, off_sub) + nlami = numpy.size(fit[i]['lambdau']) predmat[which, 0:nlami] = preds nlams.append(nlami) # convert nlams to scipy array - nlams = scipy.array(nlams, dtype = scipy.integer) + nlams = numpy.array(nlams, dtype = numpy.integer) - ywt = scipy.sum(y, axis = 1, keepdims = True) - y = y/scipy.tile(ywt, [1, y.shape[1]]) + ywt = numpy.sum(y, axis = 1, keepdims = True) + y = y/numpy.tile(ywt, [1, y.shape[1]]) weights = weights*ywt - N = y.shape[0] - scipy.sum(scipy.isnan(predmat[:,1,:]), axis = 0, keepdims = True) - bigY = scipy.tile(y[:, :, None], [1, 1, lambdau.size]) + N = y.shape[0] - numpy.sum(numpy.isnan(predmat[:,1,:]), axis = 0, keepdims = True) + bigY = numpy.tile(y[:, :, None], [1, 1, lambdau.size]) if ptype == 'mse': - cvraw = scipy.sum((bigY - predmat)**2, axis = 1).squeeze() + cvraw = numpy.sum((bigY - predmat)**2, axis = 1).squeeze() elif ptype == 'deviance': - predmat = scipy.minimum(scipy.maximum(predmat, prob_min), prob_max) - lp = bigY*scipy.log(predmat) - ly = bigY*scipy.log(bigY) + predmat = numpy.minimum(numpy.maximum(predmat, prob_min), prob_max) + lp = bigY*numpy.log(predmat) + ly = bigY*numpy.log(bigY) ly[y == 0] = 0 - cvraw = scipy.sum(2*(ly - lp), axis = 1).squeeze() + cvraw = numpy.sum(2*(ly - lp), axis = 1).squeeze() elif ptype == 'mae': - cvraw = scipy.sum(scipy.absolute(bigY - predmat), axis = 1).squeeze() + cvraw = numpy.sum(numpy.absolute(bigY - predmat), axis = 1).squeeze() elif ptype == 'class': - classid = scipy.zeros([y.shape[0], lambdau.size])*scipy.NaN + classid = numpy.zeros([y.shape[0], lambdau.size])*numpy.NaN for i in range(lambdau.size): classid[:, i] = glmnet_softmax(predmat[:,:,i]) classid = classid.reshape([classid.size,1]) yperm = bigY.transpose((0,2,1)) yperm = yperm.reshape([yperm.size, 1]) idx = sub2ind(yperm.shape, range(len(classid)), classid.transpose()) - cvraw = scipy.reshape(1 - yperm[idx], [-1, lambdau.size]); + cvraw = numpy.reshape(1 - yperm[idx], [-1, lambdau.size]) if grouped == True: cvob = cvcompute(cvraw, weights, foldid, nlams) @@ -92,7 +92,7 @@ def cvmultnet(fit, \ cvm = wtmean(cvraw, weights) sqccv = (cvraw - cvm)**2 - cvsd = scipy.sqrt(wtmean(sqccv, weights)/(N-1)) + cvsd = numpy.sqrt(wtmean(sqccv, weights)/(N-1)) result = dict() result['cvm'] = cvm @@ -115,16 +115,16 @@ def sub2ind(array_shape, rows, cols): #========================= def glmnet_softmax(x): d = x.shape - nas = scipy.any(scipy.isnan(x), axis = 1) - if scipy.any(nas): - pclass = scipy.zeros([d[0], 1])*scipy.NaN - if scipy.sum(nas) < d[0]: + nas = numpy.any(numpy.isnan(x), axis = 1) + if numpy.any(nas): + pclass = numpy.zeros([d[0], 1])*numpy.NaN + if numpy.sum(nas) < d[0]: pclass2 = glmnet_softmax(x[~nas, :]) pclass[~nas] = pclass2 result = pclass else: maxdist = x[:, 1] - pclass = scipy.ones([d[0], 1]) + pclass = numpy.ones([d[0], 1]) for i in range(1, d[1], 1): t = x[:, i] > maxdist pclass[t] = i diff --git a/glmnet_python/elnet.py b/glmnet_python/elnet.py index 0f462cd..4756e1a 100644 --- a/glmnet_python/elnet.py +++ b/glmnet_python/elnet.py @@ -5,7 +5,7 @@ # import packages/methods -import scipy +import numpy import ctypes from loadGlmLib import loadGlmLib @@ -17,14 +17,14 @@ def elnet(x, is_sparse, irs, pcs, y, weights, offset, gtype, parm, lempty, glmlib = loadGlmLib() # pre-process data - ybar = scipy.dot(scipy.transpose(y), weights) + ybar = numpy.dot(numpy.transpose(y), weights) ybar = ybar/sum(weights) nulldev = (y - ybar)**2 * weights # ka lst = ['covariance', 'naive'] ka = [i for i in range(len(lst)) if lst[i] == gtype] if len(ka) == 0: - raise ValueError('unrecognized type for ka'); + raise ValueError('unrecognized type for ka') else: ka = ka[0] + 1 # convert from 0-based to 1-based index for fortran # offset @@ -44,15 +44,15 @@ def elnet(x, is_sparse, irs, pcs, y, weights, offset, gtype, parm, lempty, ###################################### # force inputs into fortran order and into the correct scipy datatype copyFlag = False - x = x.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - irs = irs.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - pcs = pcs.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - y = y.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - weights = weights.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - jd = jd.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - vp = vp.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - cl = cl.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - ulam = ulam.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) + x = x.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + irs = irs.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + pcs = pcs.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + y = y.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + weights = weights.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + jd = jd.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + vp = vp.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + cl = cl.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + ulam = ulam.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) ###################################### # --------- ALLOCATE OUTPUTS --------- @@ -61,28 +61,28 @@ def elnet(x, is_sparse, irs, pcs, y, weights, offset, gtype, parm, lempty, lmu = -1 lmu_r = ctypes.c_int(lmu) # a0 - a0 = scipy.zeros([nlam], dtype = scipy.float64) - a0 = a0.astype(dtype = scipy.float64, order = 'F', copy = False) + a0 = numpy.zeros([nlam], dtype = numpy.float64) + a0 = a0.astype(dtype = numpy.float64, order = 'F', copy = False) a0_r = a0.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # ca - ca = scipy.zeros([nx, nlam], dtype = scipy.float64) - ca = ca.astype(dtype = scipy.float64, order = 'F', copy = False) + ca = numpy.zeros([nx, nlam], dtype = numpy.float64) + ca = ca.astype(dtype = numpy.float64, order = 'F', copy = False) ca_r = ca.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # ia - ia = -1*scipy.ones([nx], dtype = scipy.int32) - ia = ia.astype(dtype = scipy.int32, order = 'F', copy = False) + ia = -1*numpy.ones([nx], dtype = numpy.int32) + ia = ia.astype(dtype = numpy.int32, order = 'F', copy = False) ia_r = ia.ctypes.data_as(ctypes.POINTER(ctypes.c_int)) # nin - nin = -1*scipy.ones([nlam], dtype = scipy.int32) - nin = nin.astype(dtype = scipy.int32, order = 'F', copy = False) + nin = -1*numpy.ones([nlam], dtype = numpy.int32) + nin = nin.astype(dtype = numpy.int32, order = 'F', copy = False) nin_r = nin.ctypes.data_as(ctypes.POINTER(ctypes.c_int)) # rsq - rsq = -1*scipy.ones([nlam], dtype = scipy.float64) - rsq = rsq.astype(dtype = scipy.float64, order = 'F', copy = False) + rsq = -1*numpy.ones([nlam], dtype = numpy.float64) + rsq = rsq.astype(dtype = numpy.float64, order = 'F', copy = False) rsq_r = rsq.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # alm - alm = -1*scipy.ones([nlam], dtype = scipy.float64) - alm = alm.astype(dtype = scipy.float64, order = 'F', copy = False) + alm = -1*numpy.ones([nlam], dtype = numpy.float64) + alm = alm.astype(dtype = numpy.float64, order = 'F', copy = False) alm_r = alm.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # nlp nlp = -1 @@ -186,21 +186,21 @@ def elnet(x, is_sparse, irs, pcs, y, weights, offset, gtype, parm, lempty, ninmax = max(nin) # fix first value of alm (from inf to correct value) if lempty: - t1 = scipy.log(alm[1]) - t2 = scipy.log(alm[2]) - alm[0] = scipy.exp(2*t1 - t2) + t1 = numpy.log(alm[1]) + t2 = numpy.log(alm[2]) + alm[0] = numpy.exp(2*t1 - t2) # create return fit dictionary if ninmax > 0: ca = ca[0:ninmax, :] - df = scipy.sum(scipy.absolute(ca) > 0, axis=0) + df = numpy.sum(numpy.absolute(ca) > 0, axis=0) ja = ia[0:ninmax] - 1 # ia is 1-indexed in fortran - oja = scipy.argsort(ja) + oja = numpy.argsort(ja) ja1 = ja[oja] - beta = scipy.zeros([nvars, lmu], dtype = scipy.float64) + beta = numpy.zeros([nvars, lmu], dtype = numpy.float64) beta[ja1, :] = ca[oja, :] else: - beta = scipy.zeros([nvars, lmu], dtype = scipy.float64) - df = scipy.zeros([1, lmu], dtype = scipy.float64) + beta = numpy.zeros([nvars, lmu], dtype = numpy.float64) + df = numpy.zeros([1, lmu], dtype = numpy.float64) fit = dict() fit['a0'] = a0 @@ -211,7 +211,7 @@ def elnet(x, is_sparse, irs, pcs, y, weights, offset, gtype, parm, lempty, fit['lambdau'] = alm fit['npasses'] = nlp_r.value fit['jerr'] = jerr_r.value - fit['dim'] = scipy.array([nvars, lmu], dtype = scipy.integer) + fit['dim'] = numpy.array([nvars, lmu], dtype = numpy.integer) fit['offset'] = is_offset fit['class'] = 'elnet' diff --git a/glmnet_python/fishnet.py b/glmnet_python/fishnet.py index 7b022ab..e0a3724 100644 --- a/glmnet_python/fishnet.py +++ b/glmnet_python/fishnet.py @@ -3,7 +3,7 @@ Internal function called by glmnet. See also glmnet, cvglmnet """ # import packages/methods -import scipy +import numpy import ctypes from loadGlmLib import loadGlmLib @@ -14,7 +14,7 @@ def fishnet(x, is_sparse, irs, pcs, y, weights, offset, parm, # load shared fortran library glmlib = loadGlmLib() - if scipy.any( y < 0): + if numpy.any( y < 0): raise ValueError('negative responses not permitted for Poisson family') if len(offset) == 0: @@ -30,16 +30,16 @@ def fishnet(x, is_sparse, irs, pcs, y, weights, offset, parm, ###################################### # force inputs into fortran order and scipy float64 copyFlag = False - x = x.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - irs = irs.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - pcs = pcs.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - y = y.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - weights = weights.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - offset = offset.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - jd = jd.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - vp = vp.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - cl = cl.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - ulam = ulam.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) + x = x.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + irs = irs.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + pcs = pcs.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + y = y.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + weights = weights.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + offset = offset.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + jd = jd.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + vp = vp.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + cl = cl.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + ulam = ulam.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) ###################################### # --------- ALLOCATE OUTPUTS --------- @@ -48,28 +48,28 @@ def fishnet(x, is_sparse, irs, pcs, y, weights, offset, parm, lmu = -1 lmu_r = ctypes.c_int(lmu) # a0 - a0 = scipy.zeros([nlam], dtype = scipy.float64) - a0 = a0.astype(dtype = scipy.float64, order = 'F', copy = False) + a0 = numpy.zeros([nlam], dtype = numpy.float64) + a0 = a0.astype(dtype = numpy.float64, order = 'F', copy = False) a0_r = a0.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # ca - ca = scipy.zeros([nx, nlam], dtype = scipy.float64) - ca = ca.astype(dtype = scipy.float64, order = 'F', copy = False) + ca = numpy.zeros([nx, nlam], dtype = numpy.float64) + ca = ca.astype(dtype = numpy.float64, order = 'F', copy = False) ca_r = ca.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # ia - ia = -1*scipy.ones([nx], dtype = scipy.int32) - ia = ia.astype(dtype = scipy.int32, order = 'F', copy = False) + ia = -1*numpy.ones([nx], dtype = numpy.int32) + ia = ia.astype(dtype = numpy.int32, order = 'F', copy = False) ia_r = ia.ctypes.data_as(ctypes.POINTER(ctypes.c_int)) # nin - nin = -1*scipy.ones([nlam], dtype = scipy.int32) - nin = nin.astype(dtype = scipy.int32, order = 'F', copy = False) + nin = -1*numpy.ones([nlam], dtype = numpy.int32) + nin = nin.astype(dtype = numpy.int32, order = 'F', copy = False) nin_r = nin.ctypes.data_as(ctypes.POINTER(ctypes.c_int)) # dev - dev = -1*scipy.ones([nlam], dtype = scipy.float64) - dev = dev.astype(dtype = scipy.float64, order = 'F', copy = False) + dev = -1*numpy.ones([nlam], dtype = numpy.float64) + dev = dev.astype(dtype = numpy.float64, order = 'F', copy = False) dev_r = dev.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # alm - alm = -1*scipy.ones([nlam], dtype = scipy.float64) - alm = alm.astype(dtype = scipy.float64, order = 'F', copy = False) + alm = -1*numpy.ones([nlam], dtype = numpy.float64) + alm = alm.astype(dtype = numpy.float64, order = 'F', copy = False) alm_r = alm.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # nlp nlp = -1 @@ -176,22 +176,22 @@ def fishnet(x, is_sparse, irs, pcs, y, weights, offset, parm, ninmax = max(nin) # fix first value of alm (from inf to correct value) if ulam[0] == 0.0: - t1 = scipy.log(alm[1]) - t2 = scipy.log(alm[2]) - alm[0] = scipy.exp(2*t1 - t2) + t1 = numpy.log(alm[1]) + t2 = numpy.log(alm[2]) + alm[0] = numpy.exp(2*t1 - t2) # create return fit dictionary - dd = scipy.array([nvars, lmu], dtype = scipy.integer) + dd = numpy.array([nvars, lmu], dtype = numpy.integer) if ninmax > 0: ca = ca[0:ninmax, :] - df = scipy.sum(scipy.absolute(ca) > 0, axis = 0) + df = numpy.sum(numpy.absolute(ca) > 0, axis = 0) ja = ia[0:ninmax] - 1 # ia is 1-indexed in fortran - oja = scipy.argsort(ja) + oja = numpy.argsort(ja) ja1 = ja[oja] - beta = scipy.zeros([nvars, lmu], dtype = scipy.float64) + beta = numpy.zeros([nvars, lmu], dtype = numpy.float64) beta[ja1, :] = ca[oja, :] else: - beta = scipy.zeros([nvars, lmu], dtype = scipy.float64) - df = scipy.zeros([1, lmu], dtype = scipy.float64) + beta = numpy.zeros([nvars, lmu], dtype = numpy.float64) + df = numpy.zeros([1, lmu], dtype = numpy.float64) fit = dict() fit['a0'] = a0 diff --git a/glmnet_python/glmnet.py b/glmnet_python/glmnet.py index 5d46e1e..db75f05 100644 --- a/glmnet_python/glmnet.py +++ b/glmnet_python/glmnet.py @@ -87,59 +87,59 @@ class Type of regression - internal usage EXAMPLES: -------- # Gaussian - x = scipy.random.rand(100, 10) - y = scipy.random.rand(100, 1) + x = numpy.random.rand(100, 10) + y = numpy.random.rand(100, 1) fit = glmnet(x = x, y = y) fit = glmnet(x = x, y = y, alpha = 0.5) glmnetPrint(fit) - glmnetPredict(fit, scipy.empty([0]), scipy.array([0.01]), 'coef') # extract coefficients at a single value of lambdau - glmnetPredict(fit, x[0:10,:], scipy.array([0.01, 0.005])) # make predictions + glmnetPredict(fit, numpy.empty([0]), numpy.array([0.01]), 'coef') # extract coefficients at a single value of lambdau + glmnetPredict(fit, x[0:10,:], numpy.array([0.01, 0.005])) # make predictions # Multivariate Gaussian: - x = scipy.random.rand(100, 10) - y = scipy.random.rand(100,3) + x = numpy.random.rand(100, 10) + y = numpy.random.rand(100,3) fit = glmnet(x, y, 'mgaussian') glmnetPlot(fit, 'norm', False, '2norm') # Binomial - x = scipy.random.rand(100, 10) - y = scipy.random.rand(100,1) + x = numpy.random.rand(100, 10) + y = numpy.random.rand(100,1) y = (y > 0.5)*1.0 fit = glmnet(x = x, y = y, family = 'binomial', alpha = 0.5) # Multinomial - x = scipy.random.rand(100,10) - y = scipy.random.rand(100,1) + x = numpy.random.rand(100,10) + y = numpy.random.rand(100,1) y[y < 0.3] = 1.0 y[y < 0.6] = 2.0 y[y < 1.0] = 3.0 fit = glmnet(x = x, y = y, family = 'multinomial', mtype = 'grouped') # poisson - x = scipy.random.rand(100,10) - y = scipy.random.poisson(size = [100, 1])*1.0 + x = numpy.random.rand(100,10) + y = numpy.random.poisson(size = [100, 1])*1.0 fit = glmnet(x = x, y = y, family = 'poisson') # cox N = 1000; p = 30; nzc = p/3; - x = scipy.random.normal(size = [N, p]) - beta = scipy.random.normal(size = [nzc, 1]) - fx = scipy.dot(x[:, 0:nzc], beta/3) - hx = scipy.exp(fx) - ty = scipy.random.exponential(scale = 1/hx, size = [N, 1]) - tcens = scipy.random.binomial(1, 0.3, size = [N, 1]) + x = numpy.random.normal(size = [N, p]) + beta = numpy.random.normal(size = [nzc, 1]) + fx = numpy.dot(x[:, 0:nzc], beta/3) + hx = numpy.exp(fx) + ty = numpy.random.exponential(scale = 1/hx, size = [N, 1]) + tcens = numpy.random.binomial(1, 0.3, size = [N, 1]) tcens = 1 - tcens - y = scipy.column_stack((ty, tcens)) + y = numpy.column_stack((ty, tcens)) fit = glmnet(x = x.copy(), y = y.copy(), family = 'cox') glmnetPlot(fit) # sparse example N = 1000000; - x = scipy.random.normal(size = [N,10]) + x = numpy.random.normal(size = [N,10]) x[x < 3.0] = 0.0 - xs = scipy.sparse.csc_matrix(x, dtype = scipy.float64) - y = scipy.random.binomial(1, 0.5, size =[N,1]) + xs = numpy.sparse.csc_matrix(x, dtype = numpy.float64) + y = numpy.random.binomial(1, 0.5, size =[N,1]) y = y*1.0 st = time.time() fit = glmnet.glmnet(x = xs, y = y, family = 'binomial') @@ -236,7 +236,8 @@ class Type of regression - internal usage # import packages/methods from glmnetSet import glmnetSet from glmnetControl import glmnetControl -import scipy +import numpy +import scipy from elnet import elnet from lognet import lognet from coxnet import coxnet @@ -248,21 +249,21 @@ def glmnet(*, x, y, family='gaussian', **options): # check inputs: make sure x and y are scipy, float64 arrays # fortran order is not checked as we force a convert later if not( isinstance(x, scipy.sparse.csc.csc_matrix) ): - if not( isinstance(x, scipy.ndarray) and x.dtype == 'float64'): + if not( isinstance(x, numpy.ndarray) and x.dtype == 'float64'): raise ValueError('x input must be a scipy float64 ndarray') else: if not (x.dtype == 'float64'): raise ValueError('x input must be a float64 array') - if not( isinstance(y, scipy.ndarray) and y.dtype == 'float64'): + if not( isinstance(y, numpy.ndarray) and y.dtype == 'float64'): raise ValueError('y input must be a scipy float64 ndarray') # create options if options is None: - options = glmnetSet(); + options = glmnetSet() ## match the family, abbreviation allowed - fambase = ['gaussian','binomial','poisson','multinomial','cox','mgaussian']; + fambase = ['gaussian','binomial','poisson','multinomial','cox','mgaussian'] # find index of family in fambase indxtf = [x.startswith(family.lower()) for x in fambase] # find index of family in fambase famind = [i for i in range(len(indxtf)) if indxtf[i] == True] @@ -279,27 +280,27 @@ def glmnet(*, x, y, family='gaussian', **options): #print(options) ## error check options parameters - alpha = scipy.float64(options['alpha']) + alpha = numpy.float64(options['alpha']) if alpha > 1.0 : print('Warning: alpha > 1.0; setting to 1.0') - options['alpha'] = scipy.float64(1.0) + options['alpha'] = numpy.float64(1.0) if alpha < 0.0 : print('Warning: alpha < 0.0; setting to 0.0') - options['alpha'] = scipy.float64(0.0) + options['alpha'] = numpy.float64(0.0) - parm = scipy.float64(options['alpha']) - nlam = scipy.int32(options['nlambda']) + parm = numpy.float64(options['alpha']) + nlam = numpy.int32(options['nlambda']) nobs, nvars = x.shape # check weights length weights = options['weights'].copy() if len(weights) == 0: - weights = scipy.ones([nobs, 1], dtype = scipy.float64) + weights = numpy.ones([nobs, 1], dtype = numpy.float64) elif len(weights) != nobs: raise ValueError('Error: Number of elements in ''weights'' not equal to number of rows of ''x''') # check if weights are scipy nd array - if not( isinstance(weights, scipy.ndarray) and weights.dtype == 'float64'): + if not( isinstance(weights, numpy.ndarray) and weights.dtype == 'float64'): raise ValueError('weights input must be a scipy float64 ndarray') # check y length @@ -321,18 +322,18 @@ def glmnet(*, x, y, family='gaussian', **options): exclude = options['exclude'] # TBD: test this if not (len(exclude) == 0): - exclude = scipy.unique(exclude) - if scipy.any(exclude < 0) or scipy.any(exclude >= nvars): + exclude = numpy.unique(exclude) + if numpy.any(exclude < 0) or numpy.any(exclude >= nvars): raise ValueError('Error: Some excluded variables are out of range') else: - jd = scipy.append(len(exclude), exclude + 1) # indices are 1-based in fortran + jd = numpy.append(len(exclude), exclude + 1) # indices are 1-based in fortran else: - jd = scipy.zeros([1,1], dtype = scipy.integer) + jd = numpy.zeros([1,1], dtype = numpy.integer) # check vp vp = options['penalty_factor'] if len(vp) == 0: - vp = scipy.ones([1, nvars]) + vp = numpy.ones([1, nvars]) # inparms inparms = glmnetControl() @@ -345,12 +346,12 @@ def glmnet(*, x, y, family='gaussian', **options): if any(cl[1,:] < 0): raise ValueError('Error: The lower bound on cl must be non-negative') - cl[0, cl[0, :] == scipy.float64('-inf')] = -1.0*inparms['big'] - cl[1, cl[1, :] == scipy.float64('inf')] = 1.0*inparms['big'] + cl[0, cl[0, :] == numpy.float64('-inf')] = -1.0*inparms['big'] + cl[1, cl[1, :] == numpy.float64('inf')] = 1.0*inparms['big'] if cl.shape[1] < nvars: if cl.shape[1] == 1: - cl = cl*scipy.ones([1, nvars]) + cl = cl*numpy.ones([1, nvars]) else: raise ValueError('Error: Require length 1 or nvars lower and upper limits') else: @@ -358,7 +359,7 @@ def glmnet(*, x, y, family='gaussian', **options): exit_rec = 0 - if scipy.any(cl == 0.0): + if numpy.any(cl == 0.0): fdev = inparms['fdev'] if fdev != 0: optset = dict() @@ -366,12 +367,12 @@ def glmnet(*, x, y, family='gaussian', **options): glmnetControl(optset) exit_rec = 1 - isd = scipy.int32(options['standardize']) - intr = scipy.int32(options['intr']) + isd = numpy.int32(options['standardize']) + intr = numpy.int32(options['intr']) if (intr == True) and (family == 'cox'): print('Warning: Cox model has no intercept!') - jsd = scipy.int32(options['standardize_resp']) + jsd = numpy.int32(options['standardize_resp']) thresh = options['thresh'] lambdau = options['lambdau'] lambda_min = options['lambda_min'] @@ -387,16 +388,16 @@ def glmnet(*, x, y, family='gaussian', **options): if (lambda_min >= 1): raise ValueError('ERROR: lambda_min should be less than 1') flmin = lambda_min - ulam = scipy.zeros([1,1], dtype = scipy.float64) + ulam = numpy.zeros([1,1], dtype = numpy.float64) else: flmin = 1.0 if any(lambdau < 0): raise ValueError('ERROR: lambdas should be non-negative') - ulam = -scipy.sort(-lambdau) # reverse sort + ulam = -numpy.sort(-lambdau) # reverse sort nlam = lambdau.size - maxit = scipy.int32(options['maxit']) + maxit = numpy.int32(options['maxit']) gtype = options['gtype'] if len(gtype) == 0: if (nvars < 500): @@ -429,15 +430,15 @@ def glmnet(*, x, y, family='gaussian', **options): is_sparse = False if scipy.sparse.issparse(x): is_sparse = True - tx = scipy.sparse.csc_matrix(x, dtype = scipy.float64) + tx = scipy.sparse.csc_matrix(x, dtype = numpy.float64) x = tx.data; x = x.reshape([len(x), 1]) irs = tx.indices + 1 pcs = tx.indptr + 1 - irs = scipy.reshape(irs, [len(irs),]) - pcs = scipy.reshape(pcs, [len(pcs),]) + irs = numpy.reshape(irs, [len(irs),]) + pcs = numpy.reshape(pcs, [len(pcs),]) else: - irs = scipy.empty([0]) - pcs = scipy.empty([0]) + irs = numpy.empty([0]) + pcs = numpy.empty([0]) if scipy.sparse.issparse(y): y = y.todense() @@ -467,7 +468,7 @@ def glmnet(*, x, y, family='gaussian', **options): # call fishnet fit = fishnet(x, is_sparse, irs, pcs, y, weights, offset, parm, nobs, nvars, jd, vp, cl, ne, nx, nlam, flmin, ulam, - thresh, isd, intr, maxit, family); + thresh, isd, intr, maxit, family) else: raise ValueError('calling a family of fits that has not been implemented yet') diff --git a/glmnet_python/glmnetCoef.py b/glmnet_python/glmnetCoef.py index 574552a..e577c2e 100644 --- a/glmnet_python/glmnetCoef.py +++ b/glmnet_python/glmnetCoef.py @@ -14,8 +14,8 @@ Fewer input arguments (more often) are allowed in the call, but must come in the order listed above. To set default values on the way, use - scipy.empty([0]). - For example, ncoef = glmnetCoef(fit,scipy.empty([0]),False). + numpy.empty([0]). + For example, ncoef = glmnetCoef(fit,numpy.empty([0]),False). INPUT ARGUMENTS: obj Fitted "glmnet" model object. @@ -61,14 +61,14 @@ glmnet, glmnetPrint, glmnetPredict, and cvglmnet. EXAMPLES: - x = scipy.random.rand(100,20); - y = scipy.random.rand(100,1); + x = numpy.random.rand(100,20); + y = numpy.random.rand(100,1); fit = glmnet(x = x.copy(),y = y.copy()); - ncoef = glmnetCoef(fit,scipy.array([0.01, 0.001])); + ncoef = glmnetCoef(fit,numpy.array([0.01, 0.001])); """ -import scipy +import numpy from glmnetPredict import glmnetPredict def glmnetCoef(obj, s = None, exact = False): @@ -79,7 +79,7 @@ def glmnetCoef(obj, s = None, exact = False): if exact and len(s) > 0: raise NotImplementedError('exact = True not implemented in glmnetCoef') - result = glmnetPredict(obj, scipy.empty([0]), s, 'coefficients') + result = glmnetPredict(obj, numpy.empty([0]), s, 'coefficients') return(result) diff --git a/glmnet_python/glmnetControl.py b/glmnet_python/glmnetControl.py index fae3b60..2f79dcf 100644 --- a/glmnet_python/glmnetControl.py +++ b/glmnet_python/glmnetControl.py @@ -70,26 +70,26 @@ """ def glmnetControl(pars = None): - import scipy + import numpy # default options - ivals = dict(); - ivals["fdev"] = scipy.float64(1e-5) - ivals["devmax"] = scipy.float64(0.999) - ivals["eps"] = scipy.float64(1e-6) - ivals["big"] = scipy.float64(9.9e35) - ivals["mnlam"] = scipy.float64(5) - ivals["pmin"] = scipy.float64(1e-5) - ivals["exmx"] = scipy.float64(250) - ivals["prec"] = scipy.float64(1e-10) - ivals["mxit"] = scipy.float64(100) + ivals = dict() + ivals["fdev"] = numpy.float64(1e-5) + ivals["devmax"] = numpy.float64(0.999) + ivals["eps"] = numpy.float64(1e-6) + ivals["big"] = numpy.float64(9.9e35) + ivals["mnlam"] = numpy.float64(5) + ivals["pmin"] = numpy.float64(1e-5) + ivals["exmx"] = numpy.float64(250) + ivals["prec"] = numpy.float64(1e-10) + ivals["mxit"] = numpy.float64(100) # quick return if no user opts if pars == None: return ivals # if options are passed in by user, update options with values from opts - parsInIvals = set(pars.keys()) - set(ivals.keys()); + parsInIvals = set(pars.keys()) - set(ivals.keys()) if len(parsInIvals) > 0: # assert 'opts' keys are subsets of 'options' keys raise ValueError('attempting to set glmnet controls that are not known to glmnetControl') else: diff --git a/glmnet_python/glmnetPlot.py b/glmnet_python/glmnetPlot.py index 76b9437..a1dcc9d 100644 --- a/glmnet_python/glmnetPlot.py +++ b/glmnet_python/glmnetPlot.py @@ -59,10 +59,10 @@ EXAMPLES: import matplotlib.pyplot as plt - scipy.random.seed(1) - x=scipy.random.normal(size = (100,20)) - y=scipy.random.normal(size = (100,1)) - g4=scipy.random.choice(4,size = (100,1))*1.0 + numpy.random.seed(1) + x=numpy.random.normal(size = (100,20)) + y=numpy.random.normal(size = (100,1)) + g4=numpy.random.choice(4,size = (100,1))*1.0 fit1=glmnet(x = x.copy(),y = y.copy()) glmnetPlot(fit1) plt.figure() @@ -71,7 +71,7 @@ plt.figure() glmnetPlot(fit3) """ -import scipy +import numpy def glmnetPlot(x, xvar = 'norm', label = False, ptype = 'coef', **options): @@ -93,7 +93,7 @@ def glmnetPlot(x, xvar = 'norm', label = False, ptype = 'coef', **options): for i in range(len(beta)): which = nonzeroCoef(beta[i]) nzbeta[i] = beta[i][which, :] - norm = norm + scipy.sum(scipy.absolute(nzbeta[i]), axis = 0) + norm = norm + numpy.sum(numpy.absolute(nzbeta[i]), axis = 0) else: norm = 0 @@ -114,19 +114,19 @@ def glmnetPlot(x, xvar = 'norm', label = False, ptype = 'coef', **options): if i < ncl - 1: plt.figure() else: - dfseq = scipy.round_(scipy.mean(x['dfmat'], axis = 0)) + dfseq = numpy.round_(numpy.mean(x['dfmat'], axis = 0)) coefnorm = beta[1]*0 for i in range(len(beta)): - coefnorm = coefnorm + scipy.absolute(beta[i])**2 - coefnorm = scipy.sqrt(coefnorm) + coefnorm = coefnorm + numpy.absolute(beta[i])**2 + coefnorm = numpy.sqrt(coefnorm) if x['class'] == 'multnet': mstr = 'Coefficient 2Norms' handle = plotCoef(coefnorm, norm, x['lambdau'], dfseq, x['dev'], - label, xvar, '',mstr, **options); + label, xvar, '',mstr, **options) else: mstr = 'Coefficient 2Norms' handle = plotCoef(coefnorm, norm, x['lambdau'], x['dfmat'][0,:], x['dev'], - label, xvar, '', mstr, **options); + label, xvar, '', mstr, **options) return(handle) # end of glmnetplot @@ -146,11 +146,11 @@ def getFromList(xvar, xvarbase, errMsg): # end of getFromList() # ========================================= def nonzeroCoef(beta, bystep = False): - result = scipy.absolute(beta) > 0 + result = numpy.absolute(beta) > 0 if len(result.shape) == 1: - result = scipy.reshape(result, [result.shape[0], 1]) + result = numpy.reshape(result, [result.shape[0], 1]) if not bystep: - result = scipy.any(result, axis = 1) + result = numpy.any(result, axis = 1) return(result) # end of nonzeroCoef() @@ -169,12 +169,12 @@ def plotCoef(beta, norm, lambdau, df, dev, label, xvar, xlab, ylab, **options): beta = beta[which, :] if xvar == 'norm': if len(norm) == 0: - index = scipy.sum(scipy.absolute(beta), axis = 0) + index = numpy.sum(numpy.absolute(beta), axis = 0) else: index = norm iname = 'L1 Norm' elif xvar == 'lambda': - index = scipy.log(lambdau) + index = numpy.log(lambdau) iname = 'Log Lambda' elif xvar == 'dev': index = dev @@ -189,7 +189,7 @@ def plotCoef(beta, norm, lambdau, df, dev, label, xvar, xlab, ylab, **options): ax1 = plt.gca() # plot x vs y - beta = scipy.transpose(beta) + beta = numpy.transpose(beta) ax1.plot(index, beta, **options) ax2 = ax1.twiny() @@ -199,7 +199,7 @@ def plotCoef(beta, norm, lambdau, df, dev, label, xvar, xlab, ylab, **options): ylim1 = ax1.get_ylim() atdf = ax1.get_xticks() - indat = scipy.ones(atdf.shape, dtype = scipy.integer) + indat = numpy.ones(atdf.shape, dtype = numpy.integer) if index[-1] >= index[1]: for j in range(len(index)-1, -1, -1): indat[atdf <= index[j]] = j diff --git a/glmnet_python/glmnetPredict.py b/glmnet_python/glmnetPredict.py index 9b732a9..a09270b 100644 --- a/glmnet_python/glmnetPredict.py +++ b/glmnet_python/glmnetPredict.py @@ -56,7 +56,7 @@ DETAILS: The shape of the objects returned are different for "multinomial" objects. glmnetCoef(fit, ...) is equivalent to - glmnetPredict(fit,scipy.empty([]),scipy.empty([]),'coefficients"). + glmnetPredict(fit,numpy.empty([]),numpy.empty([]),'coefficients"). LICENSE: GPL-2 @@ -86,32 +86,33 @@ EXAMPLES: - x = scipy.random.normal(size = [100,20]) - y = scipy.random.normal(size = [100,1]) - g2 = scipy.random.choice(2, size = [100, 1])*1.0 # must be float64 - g4 = scipy.random.choice(4, size = [100, 1])*1.0 # must be float64 + x = numpy.random.normal(size = [100,20]) + y = numpy.random.normal(size = [100,1]) + g2 = numpy.random.choice(2, size = [100, 1])*1.0 # must be float64 + g4 = numpy.random.choice(4, size = [100, 1])*1.0 # must be float64 fit1 = glmnet(x = x.copy(),y = y.copy()); - print( glmnetPredict(fit1,x[0:5,:],scipy.array([0.01,0.005])) ) - print( glmnetPredict(fit1, scipy.empty([0]), scipy.empty([0]), 'coefficients') ) + print( glmnetPredict(fit1,x[0:5,:],numpy.array([0.01,0.005])) ) + print( glmnetPredict(fit1, numpy.empty([0]), numpy.empty([0]), 'coefficients') ) fit2 = glmnet(x = x.copy(), y = g2.copy(), family = 'binomial'); - print(glmnetPredict(fit2, x[2:5,:],scipy.empty([0]), 'response')) - print(glmnetPredict(fit2, scipy.empty([0]), scipy.empty([0]), 'nonzero')) + print(glmnetPredict(fit2, x[2:5,:],numpy.empty([0]), 'response')) + print(glmnetPredict(fit2, numpy.empty([0]), numpy.empty([0]), 'nonzero')) fit3 = glmnet(x = x.copy(), y = g4.copy(), family = 'multinomial'); - print(glmnetPredict(fit3, x[0:3,:], scipy.array([0.01, 0.5]), 'response')) + print(glmnetPredict(fit3, x[0:3,:], numpy.array([0.01, 0.5]), 'response')) """ -import scipy +import numpy +import scipy import scipy.interpolate def glmnetPredict(fit,\ - newx = scipy.empty([0]), \ - s = scipy.empty([0]), \ + newx = numpy.empty([0]), \ + s = numpy.empty([0]), \ ptype = 'link', \ exact = False, \ - offset = scipy.empty([0])): + offset = numpy.empty([0])): typebase = ['link', 'response', 'coefficients', 'nonzero', 'class'] indxtf = [x.startswith(ptype.lower()) for x in typebase] @@ -136,7 +137,7 @@ def glmnetPredict(fit,\ raise NotImplementedError('exact = True option is not implemented in python') # we convert newx to full here since sparse and full operations do not seem to - # be overloaded completely in scipy. + # be overloaded completely in numpy. if scipy.sparse.issparse(newx): newx = newx.todense() @@ -145,15 +146,15 @@ def glmnetPredict(fit,\ if fit['class'] == 'lognet': a0 = fit['a0'] else: - a0 = scipy.transpose(fit['a0']) + a0 = numpy.transpose(fit['a0']) - a0 = scipy.reshape(a0, [1, a0.size]) # convert to 1 x N for appending - nbeta = scipy.row_stack( (a0, fit['beta']) ) - if scipy.size(s) > 0: + a0 = numpy.reshape(a0, [1, a0.size]) # convert to 1 x N for appending + nbeta = numpy.row_stack( (a0, fit['beta']) ) + if numpy.size(s) > 0: lambdau = fit['lambdau'] lamlist = lambda_interp(lambdau, s) - nbeta = nbeta[:, lamlist['left']]*scipy.tile(scipy.transpose(lamlist['frac']), [nbeta.shape[0], 1]) \ - + nbeta[:, lamlist['right']]*( 1 - scipy.tile(scipy.transpose(lamlist['frac']), [nbeta.shape[0], 1])) + nbeta = nbeta[:, lamlist['left']]*numpy.tile(numpy.transpose(lamlist['frac']), [nbeta.shape[0], 1]) \ + + nbeta[:, lamlist['right']]*( 1 - numpy.tile(numpy.transpose(lamlist['frac']), [nbeta.shape[0], 1])) if ptype == 'coefficients': result = nbeta @@ -162,8 +163,8 @@ def glmnetPredict(fit,\ if ptype == 'nonzero': result = nonzeroCoef(nbeta[1:nbeta.shape[0], :], True) return(result) - # use scipy.sparse.hstack instead of column_stack for sparse matrices - result = scipy.dot(scipy.column_stack( (scipy.ones([newx.shape[0], 1]) , newx) ) , nbeta) + # use numpy.sparse.hstack instead of column_stack for sparse matrices + result = numpy.dot(numpy.column_stack( (numpy.ones([newx.shape[0], 1]) , newx) ) , nbeta) if fit['offset']: if len(offset) == 0: @@ -171,16 +172,16 @@ def glmnetPredict(fit,\ if offset.shape[1] == 2: offset = offset[:, 1] - result = result + scipy.tile(offset, [1, result.shape[1]]) + result = result + numpy.tile(offset, [1, result.shape[1]]) # fishnet if fit['class'] == 'fishnet' and ptype == 'response': - result = scipy.exp(result) + result = numpy.exp(result) # lognet if fit['class'] == 'lognet': if ptype == 'response': - pp = scipy.exp(-result) + pp = numpy.exp([-x for x in result]) result = 1/(1 + pp) elif ptype == 'class': result = (result > 0)*1 + (result <= 0)*0 @@ -202,13 +203,13 @@ def glmnetPredict(fit,\ lambdau = fit['lambdau'] lamlist = lambda_interp(lambdau, s) for i in range(nclass): - kbeta = scipy.row_stack( (a0[i, :], nbeta[i]) ) - kbeta = kbeta[:, lamlist['left']]*scipy.tile(scipy.transpose(lamlist['frac']), [kbeta.shape[0], 1]) \ - + kbeta[:, lamlist['right']]*( 1 - scipy.tile(scipy.transpose(lamlist['frac']), [kbeta.shape[0], 1])) + kbeta = numpy.row_stack( (a0[i, :], nbeta[i]) ) + kbeta = kbeta[:, lamlist['left']]*numpy.tile(numpy.transpose(lamlist['frac']), [kbeta.shape[0], 1]) \ + + kbeta[:, lamlist['right']]*( 1 - numpy.tile(numpy.transpose(lamlist['frac']), [kbeta.shape[0], 1])) nbeta[i] = kbeta else: for i in range(nclass): - nbeta[i] = scipy.row_stack( (a0[i, :], nbeta[i]) ) + nbeta[i] = numpy.row_stack( (a0[i, :], nbeta[i]) ) nlambda = len(fit['lambdau']) if ptype == 'coefficients': @@ -228,33 +229,33 @@ def glmnetPredict(fit,\ return(result) npred = newx.shape[0] - dp = scipy.zeros([nclass, nlambda, npred], dtype = scipy.float64) + dp = numpy.zeros([nclass, nlambda, npred], dtype = numpy.float64) for i in range(nclass): - qq = scipy.column_stack( (scipy.ones([newx.shape[0], 1]), newx) ) - fitk = scipy.dot( qq, nbeta[i] ) - dp[i, :, :] = dp[i, :, :] + scipy.reshape(scipy.transpose(fitk), [1, nlambda, npred]) + qq = numpy.column_stack( (numpy.ones([newx.shape[0], 1]), newx) ) + fitk = numpy.dot( qq, nbeta[i] ) + dp[i, :, :] = dp[i, :, :] + numpy.reshape(numpy.transpose(fitk), [1, nlambda, npred]) if fit['offset']: if len(offset) == 0: raise ValueError('No offset provided for prediction, yet used in fit of glmnet') if offset.shape[1] != nclass: raise ValueError('Offset should be dimension %d x %d' % (npred, nclass)) - toff = scipy.transpose(offset) + toff = numpy.transpose(offset) for i in range(nlambda): dp[:, i, :] = dp[:, i, :] + toff if ptype == 'response': - pp = scipy.exp(dp) - psum = scipy.sum(pp, axis = 0, keepdims = True) - result = scipy.transpose(pp/scipy.tile(psum, [nclass, 1, 1]), [2, 0, 1]) + pp = numpy.exp(dp) + psum = numpy.sum(pp, axis = 0, keepdims = True) + result = numpy.transpose(pp/numpy.tile(psum, [nclass, 1, 1]), [2, 0, 1]) if ptype == 'link': - result = scipy.transpose(dp, [2, 0, 1]) + result = numpy.transpose(dp, [2, 0, 1]) if ptype == 'class': - dp = scipy.transpose(dp, [2, 0, 1]) + dp = numpy.transpose(dp, [2, 0, 1]) result = list() for i in range(dp.shape[2]): t = softmax(dp[:, :, i]) - result = scipy.append(result, fit['label'][t['pclass']]) + result = numpy.append(result, fit['label'][t['pclass']]) # coxnet if fit['class'] == 'coxnet': @@ -262,8 +263,8 @@ def glmnetPredict(fit,\ if len(s) > 0: lambdau = fit['lambdau'] lamlist = lambda_interp(lambdau, s) - nbeta = nbeta[:, lamlist['left']]*scipy.tile(scipy.transpose(lamlist['frac']), [nbeta.shape[0], 1]) \ - + nbeta[:, lamlist['right']]*( 1 - scipy.tile(scipy.transpose(lamlist['frac']), [nbeta.shape[0], 1])) + nbeta = nbeta[:, lamlist['left']]*numpy.tile(numpy.transpose(lamlist['frac']), [nbeta.shape[0], 1]) \ + + nbeta[:, lamlist['right']]*( 1 - numpy.tile(numpy.transpose(lamlist['frac']), [nbeta.shape[0], 1])) if ptype == 'coefficients': result = nbeta @@ -273,16 +274,16 @@ def glmnetPredict(fit,\ result = nonzeroCoef(nbeta, True) return(result) - result = scipy.dot(newx, nbeta) + result = numpy.dot(newx, nbeta) if fit['offset']: if len(offset) == 0: raise ValueError('No offset provided for prediction, yet used in fit of glmnet') - result = result + scipy.tile(offset, [1, result.shape[1]]) + result = result + numpy.tile(offset, [1, result.shape[1]]) if ptype == 'response': - result = scipy.exp(result) + result = numpy.exp(result) return(result) @@ -302,18 +303,18 @@ def lambda_interp(lambdau, s): # sfrac*left+(1-sfrac*right) if len(lambdau) == 1: nums = len(s) - left = scipy.zeros([nums, 1], dtype = scipy.integer) + left = numpy.zeros([nums, 1], dtype = numpy.integer) right = left - sfrac = scipy.zeros([nums, 1], dtype = scipy.float64) + sfrac = numpy.zeros([nums, 1], dtype = numpy.float64) else: - s[s > scipy.amax(lambdau)] = scipy.amax(lambdau) - s[s < scipy.amin(lambdau)] = scipy.amin(lambdau) + s[s > numpy.amax(lambdau)] = numpy.amax(lambdau) + s[s < numpy.amin(lambdau)] = numpy.amin(lambdau) k = len(lambdau) sfrac = (lambdau[0] - s)/(lambdau[0] - lambdau[k - 1]) lambdau = (lambdau[0] - lambdau)/(lambdau[0] - lambdau[k - 1]) coord = scipy.interpolate.interp1d(lambdau, range(k))(sfrac) - left = scipy.floor(coord).astype(scipy.integer, copy = False) - right = scipy.ceil(coord).astype(scipy.integer, copy = False) + left = numpy.floor(coord).astype(numpy.integer, copy = False) + right = numpy.ceil(coord).astype(numpy.integer, copy = False) # tf = left != right sfrac[tf] = (sfrac[tf] - lambdau[right[tf]])/(lambdau[left[tf]] - lambdau[right[tf]]) @@ -334,14 +335,14 @@ def lambda_interp(lambdau, s): def softmax(x, gap = False): d = x.shape maxdist = x[:, 0] - pclass = scipy.zeros([d[0], 1], dtype = scipy.integer) + pclass = numpy.zeros([d[0], 1], dtype = numpy.integer) for i in range(1, d[1], 1): l = x[:, i] > maxdist pclass[l] = i maxdist[l] = x[l, i] if gap == True: - x = scipy.absolute(maxdist - x) - x[0:d[0], pclass] = x*scipy.ones([d[1], d[1]]) + x = numpy.absolute(maxdist - x) + x[0:d[0], pclass] = x*numpy.ones([d[1], d[1]]) #gaps = pmin(x)# not sure what this means; gap is never called with True raise ValueError('gap = True is not implemented yet') @@ -351,15 +352,15 @@ def softmax(x, gap = False): #result['gaps'] = gaps raise ValueError('gap = True is not implemented yet') else: - result['pclass'] = pclass; + result['pclass'] = pclass return(result) # end of softmax # ========================================= def nonzeroCoef(beta, bystep = False): - result = scipy.absolute(beta) > 0 + result = numpy.absolute(beta) > 0 if not bystep: - result = scipy.any(result, axis = 1) + result = numpy.any(result, axis = 1) return(result) # end of nonzeroCoef # ========================================= diff --git a/glmnet_python/glmnetSet.py b/glmnet_python/glmnetSet.py index 9f7b9ca..5456395 100644 --- a/glmnet_python/glmnetSet.py +++ b/glmnet_python/glmnetSet.py @@ -29,8 +29,8 @@ # given values for these parameters. options = glmnetSet( alpha = 0.1, \ intr = False, \ - maxit = scipy.int32(1e6), \ - offset = scipy.empty([0]) ) + maxit = numpy.int32(1e6), \ + offset = numpy.empty([0]) ) # same as previous case, except we pass in a # dict() object instead opts = dict(); opts['alpha'] = 0.5; @@ -40,17 +40,17 @@ Parameter Default value Description .................................................................. -alpha +alpha The elasticnet mixing parameter, with 0 < alpha <= 1. The penalty is defined as (1-alpha)/2(||beta||_2)^2+alpha||beta||_1. Default is alpha = 1, which is the lasso penalty; Currently alpha = 0 the ridge penalty. -nlambda +nlambda The number of lambda values - default is -lambdau +lambdau A user supplied lambda sequence. Typical usage is to have the program compute its own lambda sequence based on nlambda and lambda_min. Supplying a value of @@ -70,7 +70,7 @@ details below for y standardization with family='gaussian'. -weights +weights Observation weights. Can be total counts if responses are proportion matrices. Default is 1 for each observation. @@ -79,7 +79,7 @@ Should intercept(s) be fitted (default=true) or set to zero (false). -offset +offset A vector of length nobs that is included in the linear predictor (a nobs x nc matrix for the "multinomial" family). Useful for the "poisson" @@ -88,7 +88,7 @@ supplied, then values must also be supplied to the predict function. -lambda_min +lambda_min Smallest value for lambda, as a fraction of lambda_max, the (data derived) entry value (i.e., the smallest value for which all coefficients are zero). @@ -101,28 +101,28 @@ and glmnet will exit gracefully when the percentage deviance explained is almost 1. -thresh +thresh Convergence threshold for coordinate descent. Each inner coordinate-descent loop continues until the maximum change in the objective after any coefficient update is less than thresh times the null deviance. Defaults value is 1E-4. -dfmax +dfmax Limit the maximum number of variables in the model. Useful for very large nvars, if a partial path is desired. Default is nvars + 1. -pmax +pmax Limit the maximum number of variables ever to be nonzero. Default is min(dfmax * 2 + 20, nvars). -exclude +exclude Indices of variables to be excluded from the model. Default is none. Equivalent to an infinite penalty factor (next item). -penalty_factor +penalty_factor Separate penalty factors can be applied to each coefficient. This is a number that multiplies lambda to allow differential shrinkage. Can be 0 for some @@ -133,11 +133,11 @@ factors are internally rescaled to sum to nvars, and the lambda sequence will reflect this change. -maxit +maxit Maximum number of passes over the data for all lambda values; default is 10^5. -cl +cl Two-row matrix with the first row being the lower limits for each coefficient and the second the upper limits. Can be presented as a single column (which @@ -187,25 +187,25 @@ """ def glmnetSet(opts = None): - import scipy + import numpy # default options options = { - "weights" : scipy.empty([0]), - "offset" : scipy.empty([0]), - "alpha" : scipy.float64(1.0), - "nlambda" : scipy.int32(100), - "lambda_min" : scipy.empty([0]), - "lambdau" : scipy.empty([0]), + "weights" : numpy.empty([0]), + "offset" : numpy.empty([0]), + "alpha" : numpy.float64(1.0), + "nlambda" : numpy.int32(100), + "lambda_min" : numpy.empty([0]), + "lambdau" : numpy.empty([0]), "standardize" : True, "intr" : True, - "thresh" : scipy.float64(1e-7), - "dfmax" : scipy.empty([0]), - "pmax" : scipy.empty([0]), - "exclude" : scipy.empty([0], dtype = scipy.integer), - "penalty_factor" : scipy.empty([0]), - "cl" : scipy.array([[scipy.float64(-scipy.inf)], [scipy.float64(scipy.inf)]]), - "maxit" : scipy.int32(1e5), + "thresh" : numpy.float64(1e-7), + "dfmax" : numpy.empty([0]), + "pmax" : numpy.empty([0]), + "exclude" : numpy.empty([0], dtype = numpy.integer), + "penalty_factor" : numpy.empty([0]), + "cl" : numpy.array([[numpy.float64(-numpy.inf)], [numpy.float64(numpy.inf)]]), + "maxit" : numpy.int32(1e5), "gtype" : [], "ltype" : 'Newton', "standardize_resp" : False, @@ -219,7 +219,7 @@ def glmnetSet(opts = None): return options # if options are passed in by user, update options with values from opts - optsInOptions = set(opts.keys()) - set(options.keys()); + optsInOptions = set(opts.keys()) - set(options.keys()) if len(optsInOptions) > 0: # assert 'opts' keys are subsets of 'options' keys print(optsInOptions, ' : unknown option for glmnetSet') raise ValueError('attempting to set glmnet options that are not known to glmnetSet') diff --git a/glmnet_python/lognet.py b/glmnet_python/lognet.py index d231ec7..0645fde 100644 --- a/glmnet_python/lognet.py +++ b/glmnet_python/lognet.py @@ -4,7 +4,7 @@ """ # import packages/methods -import scipy +import numpy import ctypes from loadGlmLib import loadGlmLib @@ -26,12 +26,12 @@ def lognet(x, is_sparse, irs, pcs, y, weights, offset, parm, raise ValueError('x and y have different number of rows in call to glmnet') if nc == 1: - classes, sy = scipy.unique(y, return_inverse = True) + classes, sy = numpy.unique(y, return_inverse = True) nc = len(classes) - indexes = scipy.eye(nc, nc) + indexes = numpy.eye(nc, nc) y = indexes[sy, :] else: - classes = scipy.arange(nc) + 1 # 1:nc + classes = numpy.arange(nc) + 1 # 1:nc # if family == 'binomial': if nc > 2: @@ -42,14 +42,14 @@ def lognet(x, is_sparse, irs, pcs, y, weights, offset, parm, # if (len(weights) != 0): t = weights > 0 - if ~scipy.all(t): - t = scipy.reshape(t, (len(y), )) + if ~numpy.all(t): + t = numpy.reshape(t, (len(y), )) y = y[t, :] x = x[t, :] weights = weights[t] - nobs = scipy.sum(t) + nobs = numpy.sum(t) else: - t = scipy.empty([0], dtype = scipy.integer) + t = numpy.empty([0], dtype = numpy.integer) # if len(y.shape) == 1: mv = len(y) @@ -57,7 +57,7 @@ def lognet(x, is_sparse, irs, pcs, y, weights, offset, parm, else: mv, ny = y.shape - y = y*scipy.tile(weights, (1, ny)) + y = y*numpy.tile(weights, (1, ny)) # if len(offset) == 0: @@ -71,7 +71,7 @@ def lognet(x, is_sparse, irs, pcs, y, weights, offset, parm, raise ValueError('offset should have the same number of values as observations in binominal/multinomial call to glmnet') if nc == 1: if do[1] == 1: - offset = scipy.column_stack((offset, -offset), 1) + offset = numpy.column_stack((offset, -offset)) if do[1] > 2: raise ValueError('offset should have 1 or 2 columns in binomial call to glmnet') if (family == 'multinomial') and (do[1] != nc): @@ -85,16 +85,16 @@ def lognet(x, is_sparse, irs, pcs, y, weights, offset, parm, ###################################### # force inputs into fortran order and scipy float64 copyFlag = False - x = x.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - irs = irs.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - pcs = pcs.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - y = y.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - weights = weights.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - offset = offset.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - jd = jd.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - vp = vp.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - cl = cl.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - ulam = ulam.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) + x = x.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + irs = irs.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + pcs = pcs.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + y = y.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + weights = weights.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + offset = offset.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + jd = jd.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + vp = vp.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + cl = cl.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + ulam = ulam.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) ###################################### # --------- ALLOCATE OUTPUTS --------- @@ -104,32 +104,32 @@ def lognet(x, is_sparse, irs, pcs, y, weights, offset, parm, lmu_r = ctypes.c_int(lmu) # a0, ca if nc == 1: - a0 = scipy.zeros([nlam], dtype = scipy.float64) - ca = scipy.zeros([nx, nlam], dtype = scipy.float64) + a0 = numpy.zeros([nlam], dtype = numpy.float64) + ca = numpy.zeros([nx, nlam], dtype = numpy.float64) else: - a0 = scipy.zeros([nc, nlam], dtype = scipy.float64) - ca = scipy.zeros([nx, nc, nlam], dtype = scipy.float64) + a0 = numpy.zeros([nc, nlam], dtype = numpy.float64) + ca = numpy.zeros([nx, nc, nlam], dtype = numpy.float64) # a0 - a0 = a0.astype(dtype = scipy.float64, order = 'F', copy = False) + a0 = a0.astype(dtype = numpy.float64, order = 'F', copy = False) a0_r = a0.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # ca - ca = ca.astype(dtype = scipy.float64, order = 'F', copy = False) + ca = ca.astype(dtype = numpy.float64, order = 'F', copy = False) ca_r = ca.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # ia - ia = -1*scipy.ones([nx], dtype = scipy.int32) - ia = ia.astype(dtype = scipy.int32, order = 'F', copy = False) + ia = -1*numpy.ones([nx], dtype = numpy.int32) + ia = ia.astype(dtype = numpy.int32, order = 'F', copy = False) ia_r = ia.ctypes.data_as(ctypes.POINTER(ctypes.c_int)) # nin - nin = -1*scipy.ones([nlam], dtype = scipy.int32) - nin = nin.astype(dtype = scipy.int32, order = 'F', copy = False) + nin = -1*numpy.ones([nlam], dtype = numpy.int32) + nin = nin.astype(dtype = numpy.int32, order = 'F', copy = False) nin_r = nin.ctypes.data_as(ctypes.POINTER(ctypes.c_int)) # dev - dev = -1*scipy.ones([nlam], dtype = scipy.float64) - dev = dev.astype(dtype = scipy.float64, order = 'F', copy = False) + dev = -1*numpy.ones([nlam], dtype = numpy.float64) + dev = dev.astype(dtype = numpy.float64, order = 'F', copy = False) dev_r = dev.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # alm - alm = -1*scipy.ones([nlam], dtype = scipy.float64) - alm = alm.astype(dtype = scipy.float64, order = 'F', copy = False) + alm = -1*numpy.ones([nlam], dtype = numpy.float64) + alm = alm.astype(dtype = numpy.float64, order = 'F', copy = False) alm_r = alm.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # nlp nlp = -1 @@ -243,39 +243,39 @@ def lognet(x, is_sparse, irs, pcs, y, weights, offset, parm, ninmax = max(nin) # fix first value of alm (from inf to correct value) if ulam[0] == 0.0: - t1 = scipy.log(alm[1]) - t2 = scipy.log(alm[2]) - alm[0] = scipy.exp(2*t1 - t2) + t1 = numpy.log(alm[1]) + t2 = numpy.log(alm[2]) + alm[0] = numpy.exp(2*t1 - t2) # create return fit dictionary if family == 'multinomial': - a0 = a0 - scipy.tile(scipy.mean(a0), (nc, 1)) + a0 = a0 - numpy.tile(numpy.mean(a0), (nc, 1)) dfmat = a0.copy() - dd = scipy.array([nvars, lmu], dtype = scipy.integer) + dd = numpy.array([nvars, lmu], dtype = numpy.integer) beta_list = list() if ninmax > 0: # TODO: is the reshape here done right? - ca = scipy.reshape(ca, (nx, nc, lmu)) + ca = numpy.reshape(ca, (nx, nc, lmu)) ca = ca[0:ninmax, :, :] ja = ia[0:ninmax] - 1 # ia is 1-indexed in fortran - oja = scipy.argsort(ja) + oja = numpy.argsort(ja) ja1 = ja[oja] - df = scipy.any(scipy.absolute(ca) > 0, axis=1) - df = scipy.sum(df) - df = scipy.reshape(df, (1, df.size)) + df = numpy.any(numpy.absolute(ca) > 0, axis=1) + df = numpy.sum(df) + df = numpy.reshape(df, (1, df.size)) for k in range(0, nc): - ca1 = scipy.reshape(ca[:,k,:], (ninmax, lmu)) + ca1 = numpy.reshape(ca[:,k,:], (ninmax, lmu)) cak = ca1[oja,:] - dfmat[k, :] = scipy.sum(scipy.absolute(cak) > 0, axis = 0) - beta = scipy.zeros([nvars, lmu], dtype = scipy.float64) + dfmat[k, :] = numpy.sum(numpy.absolute(cak) > 0, axis = 0) + beta = numpy.zeros([nvars, lmu], dtype = numpy.float64) beta[ja1, :] = cak beta_list.append(beta) else: for k in range(0, nc): - dfmat[k, :] = scipy.zeros([1, lmu], dtype = scipy.float64) - beta_list.append(scipy.zeros([nvars, lmu], dtype = scipy.float64)) + dfmat[k, :] = numpy.zeros([1, lmu], dtype = numpy.float64) + beta_list.append(numpy.zeros([nvars, lmu], dtype = numpy.float64)) # - df = scipy.zeros([1, lmu], dtype = scipy.float64) + df = numpy.zeros([1, lmu], dtype = numpy.float64) # if kopt == 2: grouped = True @@ -298,18 +298,18 @@ def lognet(x, is_sparse, irs, pcs, y, weights, offset, parm, fit['offset'] = is_offset fit['class'] = 'multnet' else: - dd = scipy.array([nvars, lmu], dtype = scipy.integer) + dd = numpy.array([nvars, lmu], dtype = numpy.integer) if ninmax > 0: - ca = ca[0:ninmax,:]; - df = scipy.sum(scipy.absolute(ca) > 0, axis = 0); - ja = ia[0:ninmax] - 1; # ia is 1-indexes in fortran - oja = scipy.argsort(ja) + ca = ca[0:ninmax,:] + df = numpy.sum(numpy.absolute(ca) > 0, axis = 0) + ja = ia[0:ninmax] - 1 # ia is 1-indexes in fortran + oja = numpy.argsort(ja) ja1 = ja[oja] - beta = scipy.zeros([nvars, lmu], dtype = scipy.float64); - beta[ja1, :] = ca[oja, :]; + beta = numpy.zeros([nvars, lmu], dtype = numpy.float64) + beta[ja1, :] = ca[oja, :] else: - beta = scipy.zeros([nvars,lmu], dtype = scipy.float64); - df = scipy.zeros([1,lmu], dtype = scipy.float64); + beta = numpy.zeros([nvars,lmu], dtype = numpy.float64) + df = numpy.zeros([1,lmu], dtype = numpy.float64) # fit = dict() fit['a0'] = a0 diff --git a/glmnet_python/mrelnet.py b/glmnet_python/mrelnet.py index 3613e49..5e4b473 100644 --- a/glmnet_python/mrelnet.py +++ b/glmnet_python/mrelnet.py @@ -4,7 +4,7 @@ """ # import packages/methods -import scipy +import numpy import ctypes from wtmean import wtmean from loadGlmLib import loadGlmLib @@ -19,9 +19,9 @@ def mrelnet(x, is_sparse, irs, pcs, y, weights, offset, parm, # nr = y.shape[1] wym = wtmean(y, weights) - wym = scipy.reshape(wym, (1, wym.size)) - yt2 = (y - scipy.tile(wym, (y.shape[0], 1)))**2 - nulldev = scipy.sum(wtmean(yt2,weights)*scipy.sum(weights)) + wym = numpy.reshape(wym, (1, wym.size)) + yt2 = (y - numpy.tile(wym, (y.shape[0], 1)))**2 + nulldev = numpy.sum(wtmean(yt2,weights)*numpy.sum(weights)) if len(offset) == 0: offset = y*0 @@ -39,15 +39,15 @@ def mrelnet(x, is_sparse, irs, pcs, y, weights, offset, parm, ###################################### # force inputs into fortran order and scipy float64 copyFlag = False - x = x.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - irs = irs.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - pcs = pcs.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - y = y.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - weights = weights.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - jd = jd.astype(dtype = scipy.int32, order = 'F', copy = copyFlag) - vp = vp.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - cl = cl.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) - ulam = ulam.astype(dtype = scipy.float64, order = 'F', copy = copyFlag) + x = x.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + irs = irs.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + pcs = pcs.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + y = y.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + weights = weights.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + jd = jd.astype(dtype = numpy.int32, order = 'F', copy = copyFlag) + vp = vp.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + cl = cl.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) + ulam = ulam.astype(dtype = numpy.float64, order = 'F', copy = copyFlag) ###################################### # --------- ALLOCATE OUTPUTS --------- @@ -56,28 +56,28 @@ def mrelnet(x, is_sparse, irs, pcs, y, weights, offset, parm, lmu = -1 lmu_r = ctypes.c_int(lmu) # a0 - a0 = scipy.zeros([nr, nlam], dtype = scipy.float64) - a0 = a0.astype(dtype = scipy.float64, order = 'F', copy = False) + a0 = numpy.zeros([nr, nlam], dtype = numpy.float64) + a0 = a0.astype(dtype = numpy.float64, order = 'F', copy = False) a0_r = a0.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # ca - ca = scipy.zeros([nx, nr, nlam], dtype = scipy.float64) - ca = ca.astype(dtype = scipy.float64, order = 'F', copy = False) + ca = numpy.zeros([nx, nr, nlam], dtype = numpy.float64) + ca = ca.astype(dtype = numpy.float64, order = 'F', copy = False) ca_r = ca.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # ia - ia = -1*scipy.ones([nx], dtype = scipy.int32) - ia = ia.astype(dtype = scipy.int32, order = 'F', copy = False) + ia = -1*numpy.ones([nx], dtype = numpy.int32) + ia = ia.astype(dtype = numpy.int32, order = 'F', copy = False) ia_r = ia.ctypes.data_as(ctypes.POINTER(ctypes.c_int)) # nin - nin = -1*scipy.ones([nlam], dtype = scipy.int32) - nin = nin.astype(dtype = scipy.int32, order = 'F', copy = False) + nin = -1*numpy.ones([nlam], dtype = numpy.int32) + nin = nin.astype(dtype = numpy.int32, order = 'F', copy = False) nin_r = nin.ctypes.data_as(ctypes.POINTER(ctypes.c_int)) # rsq - rsq = -1*scipy.ones([nlam], dtype = scipy.float64) - rsq = rsq.astype(dtype = scipy.float64, order = 'F', copy = False) + rsq = -1*numpy.ones([nlam], dtype = numpy.float64) + rsq = rsq.astype(dtype = numpy.float64, order = 'F', copy = False) rsq_r = rsq.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # alm - alm = -1*scipy.ones([nlam], dtype = scipy.float64) - alm = alm.astype(dtype = scipy.float64, order = 'F', copy = False) + alm = -1*numpy.ones([nlam], dtype = numpy.float64) + alm = alm.astype(dtype = numpy.float64, order = 'F', copy = False) alm_r = alm.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) # nlp nlp = -1 @@ -182,54 +182,54 @@ def mrelnet(x, is_sparse, irs, pcs, y, weights, offset, parm, ninmax = max(nin) # fix first value of alm (from inf to correct value) if ulam[0] == 0.0: - t1 = scipy.log(alm[1]) - t2 = scipy.log(alm[2]) - alm[0] = scipy.exp(2*t1 - t2) + t1 = numpy.log(alm[1]) + t2 = numpy.log(alm[2]) + alm[0] = numpy.exp(2*t1 - t2) # create return fit dictionary if nr > 1: dfmat = a0.copy() - dd = scipy.array([nvars, lmu], dtype = scipy.integer) + dd = numpy.array([nvars, lmu], dtype = numpy.integer) beta_list = list() if ninmax > 0: # TODO: is the reshape here done right? - ca = scipy.reshape(ca, (nx, nr, lmu)) + ca = numpy.reshape(ca, (nx, nr, lmu)) ca = ca[0:ninmax, :, :] ja = ia[0:ninmax] - 1 # ia is 1-indexed in fortran - oja = scipy.argsort(ja) + oja = numpy.argsort(ja) ja1 = ja[oja] - df = scipy.any(scipy.absolute(ca) > 0, axis=1) - df = scipy.sum(df, axis = 0) - df = scipy.reshape(df, (1, df.size)) + df = numpy.any(numpy.absolute(ca) > 0, axis=1) + df = numpy.sum(df, axis = 0) + df = numpy.reshape(df, (1, df.size)) for k in range(0, nr): - ca1 = scipy.reshape(ca[:,k,:], (ninmax, lmu)) + ca1 = numpy.reshape(ca[:,k,:], (ninmax, lmu)) cak = ca1[oja,:] - dfmat[k, :] = scipy.sum(scipy.absolute(cak) > 0, axis = 0) - beta = scipy.zeros([nvars, lmu], dtype = scipy.float64) + dfmat[k, :] = numpy.sum(numpy.absolute(cak) > 0, axis = 0) + beta = numpy.zeros([nvars, lmu], dtype = numpy.float64) beta[ja1, :] = cak beta_list.append(beta) else: for k in range(0, nr): - dfmat[k, :] = scipy.zeros([1, lmu], dtype = scipy.float64) - beta_list.append(scipy.zeros([nvars, lmu], dtype = scipy.float64)) + dfmat[k, :] = numpy.zeros([1, lmu], dtype = numpy.float64) + beta_list.append(numpy.zeros([nvars, lmu], dtype = numpy.float64)) # - df = scipy.zeros([1, lmu], dtype = scipy.float64) + df = numpy.zeros([1, lmu], dtype = numpy.float64) # fit = dict() fit['beta'] = beta_list fit['dfmat']= dfmat else: - dd = scipy.array([nvars, lmu], dtype = scipy.integer) + dd = numpy.array([nvars, lmu], dtype = numpy.integer) if ninmax > 0: - ca = ca[0:ninmax,:]; - df = scipy.sum(scipy.absolute(ca) > 0, axis = 0); - ja = ia[0:ninmax] - 1; # ia is 1-indexes in fortran - oja = scipy.argsort(ja) + ca = ca[0:ninmax,:] + df = numpy.sum(numpy.absolute(ca) > 0, axis = 0) + ja = ia[0:ninmax] - 1 # ia is 1-indexes in fortran + oja = numpy.argsort(ja) ja1 = ja[oja] - beta = scipy.zeros([nvars, lmu], dtype = scipy.float64); - beta[ja1, :] = ca[oja, :]; + beta = numpy.zeros([nvars, lmu], dtype = numpy.float64) + beta[ja1, :] = ca[oja, :] else: - beta = scipy.zeros([nvars,lmu], dtype = scipy.float64); - df = scipy.zeros([1,lmu], dtype = scipy.float64); + beta = numpy.zeros([nvars,lmu], dtype = numpy.float64) + df = numpy.zeros([1,lmu], dtype = numpy.float64) fit['beta'] = beta fit['a0'] = a0 diff --git a/glmnet_python/wtmean.py b/glmnet_python/wtmean.py index c3c84aa..0727fbf 100644 --- a/glmnet_python/wtmean.py +++ b/glmnet_python/wtmean.py @@ -13,25 +13,25 @@ returns nan-removed weighted mean as a 1D array of size K """ -import scipy +import numpy def wtmean(mat,weights): if len(weights.shape) == 1: - weights = scipy.reshape(weights, [scipy.size(weights), 1]) + weights = numpy.reshape(weights, [numpy.size(weights), 1]) wmat = isfinite(mat)*weights mat[isnan(mat)] = 0 swmat = mat*wmat tf = weights != 0 tf = tf[:,0] - y = scipy.sum(swmat[tf, :], axis = 0)/scipy.sum(wmat, axis = 0) + y = numpy.sum(swmat[tf, :], axis = 0)/numpy.sum(wmat, axis = 0) return y # end of wtmean def isnan(x): - return ~scipy.isfinite(x) + return ~numpy.isfinite(x) # end of isnan def isfinite(x): - return scipy.isfinite(x) + return numpy.isfinite(x) # end of isfinite From f06d43693e09a8529b44feb44ead6324f3fd9df6 Mon Sep 17 00:00:00 2001 From: John Lees Date: Mon, 9 Mar 2020 16:44:24 +0000 Subject: [PATCH 12/14] Update version in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index febc562..fcd8e4c 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def build_extension(self, ext): subprocess.check_call(['gfortran', ext.input] + gfortran_args, cwd=self.build_temp, env=env) setup(name='glmnet_python', - version = '1.0.0', + version = '1.0.1', description = 'Python version of glmnet, from Stanford University', long_description=open('README.md').read(), url="https://github.com/johnlees/glmnet_python", From 69eaae87d6d0e6f3a2b25aa817cbc100d03ffe24 Mon Sep 17 00:00:00 2001 From: John Lees Date: Wed, 11 Mar 2020 18:56:24 +0000 Subject: [PATCH 13/14] Add install command to fortran SO --- setup.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index fcd8e4c..cc8855a 100644 --- a/setup.py +++ b/setup.py @@ -34,8 +34,12 @@ def build_extension(self, ext): env = os.environ.copy() subprocess.check_call(['gfortran', ext.input] + gfortran_args, cwd=self.build_temp, env=env) + extdir = os.path.join(os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))), + os.path.basename(ext.sourcedir)) + subprocess.check_call(['install', ext.output, extdir], cwd=self.build_temp, env=env) + setup(name='glmnet_python', - version = '1.0.1', + version = '1.0.2', description = 'Python version of glmnet, from Stanford University', long_description=open('README.md').read(), url="https://github.com/johnlees/glmnet_python", @@ -44,7 +48,6 @@ def build_extension(self, ext): license = 'GPL-2', packages=['glmnet_python'], install_requires=['joblib>=0.10.3'], - package_data={'glmnet_python': ['*.so', 'glmnet_python/*.so']}, classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Science/Research', @@ -58,4 +61,6 @@ def build_extension(self, ext): ext_modules=[GfortranExtension('GLMnet', 'glmnet_python', 'GLMnet.f', 'GLMnet.so')], cmdclass={'build_ext': GfortranBuild}, + package_data={'glmnet_python': ['*.so', 'glmnet_python/*.so']}, + zip_safe=False ) From 94101a0d88cc8a5505e61d97df552b6e5defab7a Mon Sep 17 00:00:00 2001 From: John Lees Date: Thu, 12 Mar 2020 10:02:25 +0000 Subject: [PATCH 14/14] Correct error with array introduced in glmnetPredict.py --- glmnet_python/glmnetPredict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glmnet_python/glmnetPredict.py b/glmnet_python/glmnetPredict.py index a09270b..7441bb1 100644 --- a/glmnet_python/glmnetPredict.py +++ b/glmnet_python/glmnetPredict.py @@ -181,7 +181,7 @@ def glmnetPredict(fit,\ # lognet if fit['class'] == 'lognet': if ptype == 'response': - pp = numpy.exp([-x for x in result]) + pp = numpy.exp(-result) result = 1/(1 + pp) elif ptype == 'class': result = (result > 0)*1 + (result <= 0)*0