diff --git a/fecon236/util/group.py b/fecon236/util/group.py index 6c58ca2..8013031 100644 --- a/fecon236/util/group.py +++ b/fecon236/util/group.py @@ -1,14 +1,19 @@ # Python Module for import Date : 2018-06-17 # vim: set fileencoding=utf-8 ff=unix tw=78 ai syn=python : per PEP 0263 -''' -_______________| group.py :: Group utilities +"""Group utilities -CHANGE LOG For LATEST version, see https://git.io/fecon236 -2018-06-17 Spin-off groupcotr() to futures.cftc module. -2018-06-16 Move covdiflog() to math.matrix module. -2018-06-14 Spin-off group stuff from top.py. -2018-03-12 fecon235.py, fecon235 v5.18.0312, https://git.io/fecon235 -''' +Notes +----- +For LATEST version, see https://git.io/fecon236 + +Change Log +---------- + +* 2018-06-17 Spin-off `groupcotr` to `futures.cftc` module. +* 2018-06-16 Move `covdiflog` to `math.matrix` module. +* 2018-06-14 Spin-off group stuff from `top.py`. +* 2018-03-12 `fecon235.py`, fecon235 v5.18.0312, https://git.io/fecon235 +""" from __future__ import absolute_import, print_function, division @@ -36,7 +41,7 @@ def groupget(ggdic=group4d, maxi=0): - '''Retrieve and create group dataframe, given group dictionary.''' + """Retrieve and create group dataframe, given group dictionary.""" # Since dictionaries are unordered, create SORTED list of keys: keys = [key for key in sorted(ggdic)] # Download individual dataframes as values into a dictionary: @@ -50,11 +55,16 @@ def groupget(ggdic=group4d, maxi=0): def groupfun(fun, groupdf, *pargs, **kwargs): - '''Use fun(ction) column-wise, then output new group dataframe.''' - # In mathematics, this is known as an "operator": - # a function which takes another function as argument. - # Examples of fun: pcent, normalize, etc. See grouppc() next. - # See groupget() to retrieve and create group dataframe. + """Use fun(ction) column-wise, then output new group dataframe. + + Notes + ----- + In mathematics, this is known as an "operator": + a function which takes another function as argument. + Examples of fun: `pcent`, `normalize`, etc. See `grouppc` next. + + .. seealso:: `groupget` to retrieve and create group dataframe. + """ keys = list(groupdf.columns) # Compute individual columns as dataframes in a list: out = [tool.todf(fun(tool.todf(groupdf[key]), *pargs, **kwargs)) @@ -68,24 +78,33 @@ def groupfun(fun, groupdf, *pargs, **kwargs): def grouppc(groupdf, freq=1): - '''Create overlapping pcent dataframe, given a group dataframe.''' - # See groupget() to retrieve and create group dataframe. - # Very useful to visualize as boxplot, see fred-georeturns.ipynb + """Create overlapping pcent dataframe, given a group dataframe. + + Notes + ----- + .. seealso:: `groupget` to retrieve and create group dataframe. + + Very useful to visualize as boxplot, see fred-georeturns.ipynb + """ return groupfun(tool.pcent, groupdf, freq) def groupdiflog(groupdf, lags=1): - '''Difference between lagged log(data) for columns in group dataframe.''' - # See groupget() to retrieve and create group dataframe. + """Difference between lagged log(data) for columns in group dataframe. + + .. seealso:: See `groupget` to retrieve and create group dataframe. + """ return groupfun(tool.diflog, groupdf, lags) def groupgeoret(groupdf, yearly=256, order=True): - '''Geometric mean returns, non-overlapping, for group dataframe. - Argument "yearly" refers to annual frequency, e.g. - 256 for daily trading days, 12 for monthly, 4 for quarterly. - ATTN: Use groupgemrat() instead for greater accuracy. - ''' + """Geometric mean returns, non-overlapping, for group dataframe. + + Argument `yearly` refers to annual frequency, e.g. + 256 for daily trading days, 12 for monthly, 4 for quarterly. + + .. note:: Use `groupgemrat` instead for greater accuracy. + """ keys = list(groupdf.columns) # Use list comprehension to store lists from georet(): geo = [tool.georet(tool.todf(groupdf[k]), yearly) + [k] for k in keys] @@ -97,12 +116,13 @@ def groupgeoret(groupdf, yearly=256, order=True): def groupgemrat(groupdf, yearly=256, order=False, n=2): - '''Geometric mean rates, non-overlapping, for group dataframe. - Argument "yearly" refers to annual frequency, e.g. - 256 for daily trading days, 12 for monthly, 4 for quarterly. - Output is rounded to n-decimal places. - Algorithm takes KURTOSIS into account for greater accuracy. - ''' + """Geometric mean rates, non-overlapping, for group dataframe. + + Argument `yearly` refers to annual frequency, e.g. + 256 for daily trading days, 12 for monthly, 4 for quarterly. + Output is rounded to n-decimal places. + Algorithm takes KURTOSIS into account for greater accuracy. + """ keys = list(groupdf.columns) # Use list comprehension to store lists from gemrat(): gem = [tool.roundit(gemrat(tool.todf(groupdf[k]), yearly), n, echo=False) @@ -115,7 +135,7 @@ def groupgemrat(groupdf, yearly=256, order=False, n=2): def groupholtf(groupdf, h=12, alpha=hw.hw_alpha, beta=hw.hw_beta): - '''Holt-Winters forecasts h-periods ahead from group dataframe.''' + """Holt-Winters forecasts h-periods ahead from group dataframe.""" forecasts = [] keys = list(groupdf.columns) for k in keys: diff --git a/fecon236/util/system.py b/fecon236/util/system.py index 5d7e9a5..774d695 100644 --- a/fecon236/util/system.py +++ b/fecon236/util/system.py @@ -1,30 +1,36 @@ -# Python Module for import Date : 2019-01-11 +# Python Module for import Date : 2018-06-20 # vim: set fileencoding=utf-8 ff=unix tw=78 ai syn=python : per PEP 0263 -''' -_______________| system.py :: system and date functions including specs. +"""System and date functions including specs. -Code in this module should be compatible with both Python 2 and 3 -until 2019-01-01, thereafter only python3. +.. note:: Code in this module should be compatible with both Python 2 and 3 + until 2019-01-01, thereafter only python3. For example, it is used in the preamble of fecon235 Jupyter notebooks. -REFERENCES: -- Compatible IDIOMS: http://python-future.org/compatible_idioms.html - Nice presentation. +Notes +----- +For latest version, see https://git.io/fecon236 + + +References +---------- +- Compatible IDIOMS: http://python-future.org/compatible_idioms.html + Nice presentation. - SIX module is exhaustive: https://pythonhosted.org/six/ - Single file source: https://bitbucket.org/gutworth/six + Single file source: https://bitbucket.org/gutworth/six + +Change Log +---------- -CHANGE LOG For latest version, see https://git.io/fecon236 -2019-01-11 Fix versionstr(): python3 dislikes import using exec(). -2018-06-20 Update specs(), include version for statsmodels. -2018-05-15 Include version("fecon236") to specs. -2018-04-25 Ignore "raw_input" < python3 flake8. -2018-04-21 yi_0sys module from fecon235 renamed to system. - Major flake8 fixes. Move notebook preamble to docs. -''' +* 2018-06-20 Update `specs`, include version for `statsmodels`. +* 2018-05-15 Include `version("fecon236")` to `specs`. +* 2018-04-25 Ignore `raw_input` < python3 flake8. +* 2018-04-21 `yi_0sys` module from fecon235 renamed to system. + Major flake8 fixes. Move notebook preamble to docs. +""" # py2rm from __future__ import absolute_import, print_function @@ -54,25 +60,30 @@ def getpwd(): - '''Get present working directory (Linux command is pwd). - Works cross-platform, giving absolute path. - ''' + """Get present working directory (Linux command is pwd). + + Works cross-platform, giving absolute path. + """ return os.getcwd() def program(): - '''Get name of present script; works cross-platform.''' - # Note: __file__ can get only the name of this module. + """Get name of present script; works cross-platform. + + Notes + ----- + `__file__` can get only the name of this module. + """ return os.path.basename(sys.argv[0]) def warn(message, stub="WARNING:", prefix=" !. "): - '''Write warning solely to standard error.''' + """Write warning solely to standard error.""" print(prefix, stub, program(), message, sep=' ', file=sys.stderr) def die(message, errcode=1, prefix=" !! "): - '''Gracefully KILL script, optionally specifying error code.''' + """Gracefully KILL script, optionally specifying error code.""" stub = "FATAL " + str(errcode) + ":" warn(message, stub, prefix) sys.exit(errcode) @@ -83,11 +94,12 @@ def die(message, errcode=1, prefix=" !! "): def date(hour=True, utc=True, localstr=' Local'): - '''Get date, and optionally time, as ISO string representation. - Boolean hour variable also gives minutes and seconds. - Setting utc to False will give local time instead of UTC, - then localstr can be used to indicate location. - ''' + """Get date, and optionally time, as ISO string representation. + + Boolean `hour` variable also gives minutes and seconds. + Setting `utc` to `False` will give local time instead of UTC, + then `localstr` can be used to indicate location. + """ if hour: form = "%Y-%m-%d, %H:%M:%S" else: @@ -102,39 +114,41 @@ def date(hour=True, utc=True, localstr=' Local'): def timestamp(): - '''Timestamp per strict RFC-3339 standard where timezone Z:=UTC.''' + """Timestamp per strict RFC-3339 standard where timezone Z:=UTC.""" form = "%Y-%m-%dT%H:%M:%SZ" tup = time.gmtime() return time.strftime(form, tup) def pythontup(): - '''Represent invoked Python version as an integer 3-tuple.''' - # Using sys.version is overly verbose. - # Here we get something like (2, 7, 10) which can be compared. + """Represent invoked Python version as an integer 3-tuple. + + Notes + ----- + Using sys.version is overly verbose. Here we get something like (2, 7, 10) + which can be compared. + """ return sys.version_info[:3] def versionstr(module="IPython"): - '''Represent version as a string, or None if not installed.''' + """Represent version as a string, or None if not installed.""" # Unfortunately must treat Python vs its modules differently... if module == "Python" or module == "python": ver = pythontup() return str(ver[0]) + '.' + str(ver[1]) + '.' + str(ver[2]) else: try: - # 2019-01-11 Fix: python3 dislikes import using exec(). - # exec("import " + module) - # exec("vermod = " + module + ".__version__") - mod = __import__(module) - vermod = mod.__version__ + vermod = None + exec("import " + module) + exec("vermod = " + module + ".__version__") return vermod except Exception: return None def versiontup(module="IPython"): - '''Parse version string into some integer 3-tuple.''' + """Parse version string into some integer 3-tuple.""" s = versionstr(module) try: v = [int(k) for k in s.split('.')] @@ -148,14 +162,17 @@ def versiontup(module="IPython"): def version(module="IPython"): - '''Pretty print Python or module version info.''' + """Pretty print Python or module version info.""" print(" :: ", module, versionstr(module)) def utf(immigrant, xnl=True): - '''Convert to utf-8, and possibly delete new line character. - xnl means "delete new line" - ''' + """Convert to utf-8, and possibly delete new line character. + + Notes + ----- + `xnl` means "delete new line" + """ if xnl: # Decodes to utf-8, plus deletes new line. return immigrant.decode('utf-8').strip('\n') @@ -165,25 +182,37 @@ def utf(immigrant, xnl=True): def run(command, xnl=True, errf=None): - '''RUN **quote and space insensitive** SYSTEM-LEVEL command. - OTHERWISE: use check_output directly and list component - parts of the command, e.g. - check_output(["git", "describe", "--abbrev=0"]) - then generally use our utf() since check_output - usually does not return utf-8, so be prepared to - receive bytes and also new line. - ''' - # N.B. - errf=None means the usual error transmittal. - # Cross-platform /dev/stdout is STDOUT - # Cross-platform /dev/null is our dev_null above. - # https://docs.python.org/2/library/subprocess.html + """RUN **quote and space insensitive** SYSTEM-LEVEL command. + + OTHERWISE: use `check_output` directly and list component + parts of the command, e.g. `check_output(["git", "describe", + "--abbrev=0"])` then generally use our `utf` since check_output usually + does not return utf-8, so be prepared to receive bytes and also new line. + + Notes + ----- + + - `errf=None` means the usual error transmittal. + - Cross-platform /dev/stdout is STDOUT + - Cross-platform /dev/null is our dev_null above. + + References + ---------- + + - https://docs.python.org/2/library/subprocess.html + + """ return utf(check_output(command.split(), stderr=errf), xnl) def gitinfo(): - '''From git, get repo name, current branch and annotated tag.''' - # Suppressing error messages by os.devnull seems cross-platform, - # but it is just a string, so use our open file dev_null instead. + """From git, get repo name, current branch and annotated tag. + + Notes + ----- + Suppressing error messages by `os.devnull` seems cross-platform, + but it is just a string, so use our open file dev_null instead. + """ try: repopath = run("git rev-parse --show-toplevel", errf=dev_null) # ^returns the dir path plus working repo name. @@ -199,17 +228,19 @@ def gitinfo(): def specs(): - '''Show ecosystem specifications, including execution timestamp. - APIs are subject to change, so versions are critical for replication. - ''' + """Show ecosystem specifications, including execution timestamp. + + Notes + ----- + APIs are subject to change, so versions are critical for replication. + """ if pythontup() < minimumPython: warn("This project requires a more recent Python version.") else: print(" !: Code for this project straddles python27 and python3.") version("Python") version("IPython") - version("jupyter_core") # Common functionality of Jupyter projects. - version("jupyter_client") # Jupyter client libraries and protocol. + version("jupyter_core") version("notebook") # Worked for Jupyter notebook 4.0.6 version("matplotlib") version("numpy") @@ -226,15 +257,15 @@ def specs(): def endmodule(): - '''Procedure after __main__ conditional in modules.''' + """Procedure after `__main__` conditional in modules.""" die("is a MODULE for import, not for direct execution.", 113) # py2rm -'''ROSETTA STONE FUNCTIONS approximately bridging Python 2 and 3. +"""ROSETTA STONE FUNCTIONS approximately bridging Python 2 and 3. e.g. answer = get_input("Favorite animal? ") print(answer) -''' +""" if pythontup() < (3, 0, 0): get_input = raw_input # noqa else: