├── requirements.txt ├── docs ├── source │ ├── _static │ │ └── Images │ │ │ ├── API@2x.png │ │ │ ├── total@2x.png │ │ │ ├── alt_axis@2x.png │ │ │ ├── average@2x.png │ │ │ ├── custom_fn@2x.png │ │ │ ├── format_a@2x.png │ │ │ ├── format_a_b@2x.png │ │ │ ├── format_row@2x.png │ │ │ └── format_complex@2x.png │ ├── prettypandas.formatters.rst │ ├── prettypandas.summarizer.rst │ ├── testing.rst │ ├── prettypandas.rst │ ├── index.rst │ ├── quickstart.rst │ └── conf.py ├── Makefile └── make.bat ├── prettypandas ├── __init__.py ├── formatters.py └── summarizer.py ├── .travis.yml ├── .gitignore ├── license.md ├── setup.py ├── Readme.md ├── test └── test_pretty_pandas.py └── README.txt /requirements.txt: -------------------------------------------------------------------------------- 1 | babel 2 | numpy 3 | pandas>=0.20.0 4 | -------------------------------------------------------------------------------- /docs/source/_static/Images/API@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HHammond/PrettyPandas/HEAD/docs/source/_static/Images/API@2x.png -------------------------------------------------------------------------------- /docs/source/_static/Images/total@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HHammond/PrettyPandas/HEAD/docs/source/_static/Images/total@2x.png -------------------------------------------------------------------------------- /docs/source/_static/Images/alt_axis@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HHammond/PrettyPandas/HEAD/docs/source/_static/Images/alt_axis@2x.png -------------------------------------------------------------------------------- /docs/source/_static/Images/average@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HHammond/PrettyPandas/HEAD/docs/source/_static/Images/average@2x.png -------------------------------------------------------------------------------- /docs/source/_static/Images/custom_fn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HHammond/PrettyPandas/HEAD/docs/source/_static/Images/custom_fn@2x.png -------------------------------------------------------------------------------- /docs/source/_static/Images/format_a@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HHammond/PrettyPandas/HEAD/docs/source/_static/Images/format_a@2x.png -------------------------------------------------------------------------------- /docs/source/_static/Images/format_a_b@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HHammond/PrettyPandas/HEAD/docs/source/_static/Images/format_a_b@2x.png -------------------------------------------------------------------------------- /docs/source/_static/Images/format_row@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HHammond/PrettyPandas/HEAD/docs/source/_static/Images/format_row@2x.png -------------------------------------------------------------------------------- /docs/source/_static/Images/format_complex@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HHammond/PrettyPandas/HEAD/docs/source/_static/Images/format_complex@2x.png -------------------------------------------------------------------------------- /docs/source/prettypandas.formatters.rst: -------------------------------------------------------------------------------- 1 | prettypandas.formatters module 2 | ============================== 3 | 4 | .. automodule:: prettypandas.formatters 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/prettypandas.summarizer.rst: -------------------------------------------------------------------------------- 1 | 2 | prettypandas.summarize module 3 | ============================= 4 | 5 | .. automodule:: prettypandas.summarizer 6 | :members: 7 | :undoc-members: 8 | :show-inheritance: 9 | -------------------------------------------------------------------------------- /prettypandas/__init__.py: -------------------------------------------------------------------------------- 1 | from .summarizer import PrettyPandas 2 | from .formatters import as_currency, as_percent, as_unit 3 | 4 | 5 | __all__ = [ 6 | 'PrettyPandas', 7 | 8 | 'as_currency', 9 | 'as_percent', 10 | 'as_unit', 11 | ] 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" # Min version supported by IPython 4 | - "3.4" 5 | - "3.5" 6 | - "3.7" 7 | 8 | install: 9 | - "pip install -r requirements.txt" 10 | - "python setup.py install" 11 | 12 | script: py.test test 13 | -------------------------------------------------------------------------------- /docs/source/testing.rst: -------------------------------------------------------------------------------- 1 | .. testing: 2 | 3 | Testing 4 | ======= 5 | 6 | Tests use `pytest `_ for testing. After downloading the 7 | repository from `GitHub `_ run the 8 | following: 9 | 10 | 11 | .. code-block:: sh 12 | 13 | py.test test 14 | -------------------------------------------------------------------------------- /docs/source/prettypandas.rst: -------------------------------------------------------------------------------- 1 | prettypandas package 2 | ==================== 3 | 4 | .. automodule:: prettypandas 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | prettypandas.summarizer 15 | prettypandas.formatters 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | .pytest_cache 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | *.ipynb_checkpoints 59 | 60 | # PyBuilder 61 | target/ 62 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Henry Hammond 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from codecs import open 3 | from os import path 4 | 5 | here = path.abspath(path.dirname(__file__)) 6 | 7 | with open(path.join(here, 'README.txt'), encoding='utf-8') as f: 8 | long_description = f.read() 9 | 10 | setup( 11 | name='prettypandas', 12 | 13 | version='0.0.4', 14 | 15 | description='Pandas Styler for Report Quality Tables.', 16 | long_description=long_description, 17 | 18 | url='https://github.com/HHammond/PrettyPandas', 19 | 20 | author='Henry Hammond', 21 | author_email='henryhhammond92@gmail.com', 22 | 23 | license='MIT', 24 | 25 | classifiers=[ 26 | 'Development Status :: 3 - Alpha', 27 | 28 | 'Intended Audience :: Science/Research', 29 | 'Topic :: Scientific/Engineering', 30 | 31 | 'License :: OSI Approved :: MIT License', 32 | 33 | 'Programming Language :: Python :: 2', 34 | 'Programming Language :: Python :: 2.7', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.2', 37 | 'Programming Language :: Python :: 3.3', 38 | 'Programming Language :: Python :: 3.4', 39 | 'Programming Language :: Python :: 3.5', 40 | ], 41 | 42 | keywords='pandas pretty display tables reporting', 43 | 44 | packages=["prettypandas"], 45 | 46 | install_requires=[ 47 | "babel", 48 | "numpy", 49 | "pandas >= 0.17.1" 50 | ], 51 | ) 52 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Pretty Pandas 2 | 3 | ![Testing Status](https://travis-ci.org/HHammond/PrettyPandas.svg?branch=master) 4 | [![Documentation Status](http://readthedocs.org/projects/prettypandas/badge/?version=latest)](http://prettypandas.readthedocs.org/en/latest/?badge=latest) 5 | ![Supported Versions](https://img.shields.io/pypi/pyversions/prettypandas.svg) 6 | ![PyPI](https://img.shields.io/pypi/l/prettypandas.svg) 7 | 8 | PrettyPandas is a Pandas DataFrame Styler class that helps you create 9 | report quality tables with a simple API. 10 | 11 | ```python 12 | ( 13 | df 14 | .pipe(PrettyPandas) 15 | .as_currency('GBP', subset='A') 16 | .as_percent(subset='B') 17 | .total() 18 | .average() 19 | ) 20 | ``` 21 | 22 | 23 | 24 | Features 25 | -------- 26 | 27 | - Add summary rows and columns. 28 | - A nice and customizable theme. 29 | - Number formatting for currency, scientific units, and percentages. 30 | - Chaining commands. 31 | - Works seamlessly with [Pandas Style 32 | API](http://pandas.pydata.org/pandas-docs/stable/style.html). 33 | 34 | Installation 35 | ------------ 36 | 37 | You can install PrettyPandas using `pip` with support for Python 2.7, 38 | 3.3, 3.4, and 3.5: 39 | 40 | ``` {.sourceCode .sh} 41 | pip install prettypandas 42 | ``` 43 | 44 | You can also install from source: 45 | 46 | ``` {.sourceCode .sh} 47 | git clone git@github.com:HHammond/PrettyPandas.git 48 | cd PrettyPandas 49 | python setup.py install 50 | ``` 51 | 52 | Documentation 53 | ------------- 54 | 55 | Documentation is hosted on [Read the Docs](http://prettypandas.readthedocs.org). 56 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. PrettyPandas documentation master file, created by 2 | sphinx-quickstart on Tue Jan 19 12:59:36 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | PrettyPandas 7 | ============ 8 | 9 | PrettyPandas is an extension to the Pandas DataFrame class that helps you 10 | create report qualitiy tables with a simple API. 11 | 12 | 13 | .. code-block:: python 14 | 15 | ( 16 | df 17 | .pipe(PrettyPandas) 18 | .as_currency('GBP', subset='A') 19 | .as_percent(subset='B') 20 | .total() 21 | .average() 22 | ) 23 | 24 | .. image:: _static/Images/API@2x.png 25 | :width: 400px 26 | 27 | 28 | Features 29 | -------- 30 | 31 | - Add summary rows and columns. 32 | - Number formatting for currency, scientific units, and percentages. 33 | - Chaining commands. 34 | - Works seamlessly with `Pandas Style API`_. 35 | 36 | .. note:: 37 | 38 | Version 0.0.4 removes the ``apply_pretty_globals`` function and other custom 39 | CSS properties because Pandas and Jupyter now defaults to providing great 40 | looking html tables. If you still want custom CSS you can use the `Pandas 41 | Style API`_. 42 | 43 | .. _Pandas Style API: http://pandas.pydata.org/pandas-docs/stable/style.html> 44 | 45 | Installation 46 | ------------ 47 | 48 | You can install PrettyPandas using ``pip`` with support for Python 2.7, 3.3, 49 | 3.4, and 3.5: 50 | 51 | .. code-block:: sh 52 | 53 | pip install prettypandas 54 | 55 | 56 | You can also install from source: 57 | 58 | .. code-block:: sh 59 | 60 | git clone git@github.com:HHammond/PrettyPandas.git 61 | cd PrettyPandas 62 | python setup.py install 63 | 64 | 65 | Contributing 66 | ------------ 67 | 68 | The project is available on `GitHub`_ and anyone is welcome to contribute. You 69 | can use the `issue tracker`_ to report issues, bugs, or suggest improvements. 70 | 71 | .. _GitHub: https://github.com/HHammond/PrettyPandas/ 72 | .. _issue tracker: https://github.com/HHammond/PrettyPandas/issues 73 | 74 | Contents 75 | -------- 76 | 77 | .. toctree:: 78 | :maxdepth: 2 79 | 80 | self 81 | Getting Started 82 | testing 83 | API 84 | 85 | Indices and tables 86 | ================== 87 | 88 | * :ref:`genindex` 89 | * :ref:`modindex` 90 | * :ref:`search` 91 | -------------------------------------------------------------------------------- /test/test_pretty_pandas.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | import pytest 4 | import numpy as np 5 | import pandas as pd 6 | 7 | from prettypandas import PrettyPandas 8 | 9 | 10 | @pytest.fixture() 11 | def dataframe(): 12 | np.random.seed(24) 13 | df = pd.DataFrame({'A': np.linspace(1, 10, 10)}) 14 | df = pd.concat([df, pd.DataFrame(np.random.randn(10, 4), 15 | columns=list('BCDE'))], 16 | axis=1) 17 | return df 18 | 19 | 20 | @pytest.fixture() 21 | def prettyframe(dataframe): 22 | return PrettyPandas(dataframe) 23 | 24 | 25 | def test_creation(dataframe): 26 | PrettyPandas(dataframe) 27 | 28 | try: 29 | PrettyPandas(None) 30 | except TypeError: 31 | assert True 32 | 33 | p1 = PrettyPandas(dataframe) 34 | assert p1.summary_rows == [] 35 | assert p1.summary_cols == [] 36 | assert p1.formatters == [] 37 | 38 | p2 = PrettyPandas(dataframe, summary_rows=['test']) 39 | assert p2.summary_rows == ['test'] 40 | assert p1.summary_cols == [] 41 | assert p1.formatters == [] 42 | 43 | 44 | def test_data_safety(dataframe): 45 | df1 = copy.deepcopy(dataframe) 46 | 47 | df = PrettyPandas(dataframe) 48 | df.total()._apply_summaries() 49 | 50 | assert all(dataframe == df1) 51 | assert all(df.data == df1) 52 | 53 | 54 | def test_summary(dataframe): 55 | p1 = PrettyPandas(dataframe).total() 56 | actual = list(p1.data.sum()) 57 | 58 | r = p1._apply_summaries() 59 | row = r.iloc[-1] 60 | assert (row == actual).all() 61 | 62 | 63 | def test_summary_fns(dataframe): 64 | PrettyPandas(dataframe).total() 65 | PrettyPandas(dataframe).average() 66 | PrettyPandas(dataframe).median() 67 | PrettyPandas(dataframe).max() 68 | PrettyPandas(dataframe).min() 69 | 70 | out = PrettyPandas(dataframe).total() 71 | assert len(out.summary_rows) == 1 72 | assert len(out.summary_cols) == 0 73 | 74 | out = PrettyPandas(dataframe).total(axis=1) 75 | assert len(out.summary_rows) == 0 76 | assert len(out.summary_cols) == 1 77 | 78 | out = PrettyPandas(dataframe).total(axis=None) 79 | assert len(out.summary_rows) == 1 80 | assert len(out.summary_cols) == 1 81 | 82 | out = PrettyPandas(dataframe).min().max() 83 | assert len(out.summary_rows) == 2 84 | assert len(out.summary_cols) == 0 85 | 86 | out = PrettyPandas(dataframe).min().max(axis=1) 87 | assert len(out.summary_rows) == 1 88 | assert len(out.summary_cols) == 1 89 | 90 | 91 | def test_mulitindex(): 92 | df = pd.DataFrame({'A': [1, 2], 93 | 'B': [3, 4], 94 | 'D': [4, 3], 95 | 'C': [6, 7]}) 96 | 97 | with pytest.raises(ValueError): 98 | output = PrettyPandas(df.set_index(['A', 'B'])).total(axis=1)._apply_summaries() 99 | -------------------------------------------------------------------------------- /prettypandas/formatters.py: -------------------------------------------------------------------------------- 1 | from numbers import Number, Integral 2 | from functools import partial, wraps 3 | import locale 4 | 5 | from babel import Locale, numbers 6 | 7 | 8 | LOCALE, ENCODING = locale.getlocale() 9 | LOCALE_OBJ = Locale(LOCALE or "en_US") 10 | 11 | 12 | def _surpress_formatting_errors(fn): 13 | """ 14 | I know this is dangerous and the wrong way to solve the problem, but when 15 | using both row and columns summaries it's easier to just swallow errors 16 | so users can format their tables how they need. 17 | """ 18 | @wraps(fn) 19 | def inner(*args, **kwargs): 20 | try: 21 | return fn(*args, **kwargs) 22 | except ValueError: 23 | return "" 24 | return inner 25 | 26 | 27 | def _format_numer(number_format, prefix='', suffix=''): 28 | """Format a number to a string.""" 29 | @_surpress_formatting_errors 30 | def inner(v): 31 | if isinstance(v, Number): 32 | return ("{{}}{{:{}}}{{}}" 33 | .format(number_format) 34 | .format(prefix, v, suffix)) 35 | else: 36 | raise TypeError("Numberic type required.") 37 | return inner 38 | 39 | 40 | def as_percent(precision=2, **kwargs): 41 | """Convert number to percentage string. 42 | 43 | Parameters: 44 | ----------- 45 | :param v: numerical value to be converted 46 | :param precision: int 47 | decimal places to round to 48 | """ 49 | if not isinstance(precision, Integral): 50 | raise TypeError("Precision must be an integer.") 51 | 52 | return _surpress_formatting_errors( 53 | _format_numer(".{}%".format(precision)) 54 | ) 55 | 56 | 57 | def as_unit(unit, precision=2, location='suffix'): 58 | """Convert value to unit. 59 | 60 | Parameters: 61 | ----------- 62 | :param v: numerical value 63 | :param unit: string of unit 64 | :param precision: int 65 | decimal places to round to 66 | :param location: 67 | 'prefix' or 'suffix' representing where the currency symbol falls 68 | relative to the value 69 | """ 70 | if not isinstance(precision, Integral): 71 | raise TypeError("Precision must be an integer.") 72 | 73 | if location == 'prefix': 74 | formatter = partial(_format_numer, prefix=unit) 75 | elif location == 'suffix': 76 | formatter = partial(_format_numer, suffix=unit) 77 | else: 78 | raise ValueError("location must be either 'prefix' or 'suffix'.") 79 | 80 | return _surpress_formatting_errors( 81 | formatter("0.{}f".format(precision)) 82 | ) 83 | 84 | 85 | def as_currency(currency='USD', locale=LOCALE_OBJ): 86 | @_surpress_formatting_errors 87 | def inner(v): 88 | return numbers.format_currency(v, currency=currency, locale=LOCALE_OBJ) 89 | return inner 90 | 91 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | PrettyPandas 2 | ============ 3 | 4 | PrettyPandas is an extension to the Pandas DataFrame class that helps you 5 | create report qualitiy tables with a simple API. 6 | 7 | Features 8 | -------- 9 | 10 | - Add summary rows and columns. 11 | - Number formatting for currency, scientific units, and percentages. 12 | - Chaining commands in a Fluent API. 13 | - Works seamlessly with `Pandas Style API`_. 14 | 15 | .. note:: 16 | 17 | Version 0.0.4 removes the ``apply_pretty_globals`` function and other custom 18 | CSS properties because Pandas and Jupyter now defaults to providing great 19 | looking html tables. If you still want custom CSS you can use the `Pandas 20 | Style API`_. 21 | 22 | .. _Pandas Style API: http://pandas.pydata.org/pandas-docs/stable/style.html> 23 | 24 | Installation 25 | ------------ 26 | 27 | You can install PrettyPandas using ``pip`` with support for Python 2.7, 3.3, 28 | 3.4, and 3.5: 29 | 30 | .. code-block:: sh 31 | 32 | pip install prettypandas 33 | 34 | 35 | You can also install from source: 36 | 37 | .. code-block:: sh 38 | 39 | git clone git@github.com:HHammond/PrettyPandas.git 40 | cd PrettyPandas 41 | python setup.py install 42 | 43 | 44 | Contributing 45 | ------------ 46 | 47 | The project is available on `GitHub`_ and anyone is welcome to contribute. You 48 | can use the `issue tracker`_ to report issues, bugs, or suggest improvements. 49 | 50 | .. _GitHub: https://github.com/HHammond/PrettyPandas/ 51 | .. _issue tracker: https://github.com/HHammond/PrettyPandas/issues 52 | 53 | 54 | Documentation 55 | ------------- 56 | 57 | See the `GitHub page `_ for 58 | documentation. 59 | 60 | 61 | License 62 | ------- 63 | 64 | The MIT License (MIT) 65 | 66 | Copyright (c) 2016 Henry Hammond 67 | 68 | Permission is hereby granted, free of charge, to any person obtaining a copy 69 | of this software and associated documentation files (the "Software"), to deal 70 | in the Software without restriction, including without limitation the rights 71 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 72 | copies of the Software, and to permit persons to whom the Software is 73 | furnished to do so, subject to the following conditions: 74 | 75 | The above copyright notice and this permission notice shall be included in all 76 | copies or substantial portions of the Software. 77 | 78 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 79 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 80 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 81 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 82 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 83 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 84 | SOFTWARE. 85 | -------------------------------------------------------------------------------- /docs/source/quickstart.rst: -------------------------------------------------------------------------------- 1 | .. quickstart: 2 | 3 | Quick Start 4 | =========== 5 | 6 | Adding Summaries 7 | ---------------- 8 | 9 | PrettyPandas supports many built in summary functions, as well as providing the 10 | ability to create your own summaries. Summary functions can be applied over 11 | a DataFrame's rows or columns, or both. 12 | 13 | The builtin summary methods are: 14 | 15 | * :py:meth:`total ` 16 | * :py:meth:`average ` 17 | * :py:meth:`median ` 18 | * :py:meth:`min ` 19 | * :py:meth:`max ` 20 | 21 | If you wanted to add a grand total to the bottom of your table the code is 22 | simple: 23 | 24 | .. code-block:: python 25 | 26 | PrettyPandas(df).total() 27 | 28 | .. image:: _static/Images/total@2x.png 29 | :width: 311px 30 | 31 | Or additionally if you want to use Pandas fluent API: 32 | 33 | .. code-block:: python 34 | 35 | df.pipe(PrettyPandas).total() 36 | 37 | 38 | PrettyPandas follows a fluent API so you can chain multiple summaries easily: 39 | 40 | .. code-block:: python 41 | 42 | df.pipe(PrettyPandas).total().average() 43 | 44 | .. image:: _static/Images/average@2x.png 45 | :width: 334px 46 | 47 | The ``axis`` parameter specifies which ``numpy`` style axis to apply a summary 48 | on --- 0 for columns, 1 for rows, and ``None`` for both. 49 | 50 | .. code-block:: python 51 | 52 | PrettyPandas(df).total(axis=1) 53 | 54 | .. image:: _static/Images/alt_axis@2x.png 55 | :width: 349px 56 | 57 | You can even mix and match summaries applied to different axis. 58 | 59 | Creating a Custom Summary 60 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 61 | 62 | The :py:meth:`summary ` method creates a custom summary 63 | from a function which takes an array-like structure as a list. 64 | 65 | .. code-block:: python 66 | 67 | def count_greater_than_zero(column): 68 | return (column > 0).sum() 69 | 70 | PrettyPandas(df).summary(count_greater_than_zero, title="> 0") 71 | 72 | .. image:: _static/Images/custom_fn@2x.png 73 | :width: 287px 74 | 75 | 76 | Converting Back to Pandas DataFrame 77 | ----------------------------------- 78 | 79 | ``.to_frame()`` 80 | ^^^^^^^^^^^^^^^ 81 | 82 | After adding summary rows or columns you can get a DataFrame with your changes 83 | applied by calling the ``._to_frame``. 84 | 85 | For example the following code would add a total to your DataFrame and return 86 | it back to a Pandas native DataFrame. 87 | 88 | .. code-block:: python 89 | 90 | ( 91 | df 92 | .pipe(PrettyPandas) 93 | .total(axis=1) 94 | .to_frame() 95 | ) 96 | 97 | 98 | ``.style`` 99 | ^^^^^^^^^^ 100 | 101 | The ``.style`` property allows you to drop right into the Pandas Style API. 102 | This code would allow you to compute a summary, format the table using 103 | percentages, and apply a backgrouned gradient to a table: 104 | 105 | .. code-block:: python 106 | 107 | ( 108 | df.pipe(PrettyPandas) 109 | .as_percent(precision=0) 110 | .median() 111 | .style 112 | .background_gradient() 113 | ) 114 | 115 | 116 | Formatting Numbers 117 | ------------------ 118 | 119 | Most reports use at least some units of measurement. PrettyPandas currently 120 | supports percentages, money, and a more general unit method. 121 | 122 | * :py:meth:`as_percent ` 123 | * :py:meth:`as_currency ` 124 | * :py:meth:`as_unit ` 125 | 126 | The ``as_unit`` method takes a positional ``unit`` argument which indicates the 127 | string representing the unit to be used and a ``location`` argument to specify 128 | whether the unit should be a prefix or suffix to the value. 129 | 130 | The ``as_currency`` and ``as_percent`` methods are localized to use whatever 131 | units your Python distribution thinks are best for you. If you aren't getting 132 | the correct units use the :py:meth:`set_locale 133 | ` method to specify your locale. 134 | 135 | If you need to use a different currency, just pass it to ``currency='...'`` to 136 | change it. 137 | 138 | The ``as_money`` method takes optional ``currency`` and ``location`` arguments 139 | which work just like the ``as_unit`` method. By default the currency is in 140 | dollars. 141 | 142 | .. note:: 143 | Python 2 doesn't support unicode literals by default. You can use `unicode 144 | literals`_ (e.g. ``u'€'``) or import the unicode literal behaviour from 145 | Python 3: 146 | 147 | .. code-block:: python 148 | 149 | from __future__ import unicode_literals 150 | 151 | 152 | .. _unicode literals: 153 | https://docs.python.org/2/howto/unicode.html#unicode-literals-in-python-source-code 154 | 155 | 156 | Formatting Columns 157 | ^^^^^^^^^^^^^^^^^^ 158 | 159 | By default the formatting methods apply to the entire dataframe. When you need 160 | to format just a few columns you can use the `subset` argument to specify a 161 | single column, or multiple columns. 162 | 163 | .. code-block:: python 164 | 165 | PrettyPandas(df).as_percent(subset='A') # Format just column A 166 | 167 | .. image:: _static/Images/format_a@2x.png 168 | :width: 301px 169 | 170 | .. code-block:: python 171 | 172 | PrettyPandas(df).as_percent(subset=['A', 'B']) # Format columns A and B 173 | 174 | .. image:: _static/Images/format_a_b@2x.png 175 | :width: 363px 176 | 177 | Formatting Rows and Complex Formatting 178 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 179 | 180 | Formatting rows is more complicated than formatting columns. The `subset` 181 | argument needs to take in a `pandas.Index` to specify the row. 182 | 183 | .. code-block:: python 184 | 185 | # Format the row with row-index 3 186 | PrettyPandas(df).as_percent(subset=pd.IndexSlice[3,:], precision=2) 187 | 188 | .. image:: _static/Images/format_row@2x.png 189 | :width: 294px 190 | 191 | For multi-index dataframes subsetting is more complicated. You will need to use 192 | multiple ``pandas.IndexSlice`` objects to get the correct rows. 193 | 194 | The following example shows how to select rows in a multi-index: 195 | 196 | .. code-block:: python 197 | 198 | first_row_idx = pd.IndexSlice[0, :] 199 | second_row_idx = pd.IndexSlice[1, :] 200 | 201 | ( 202 | df.pipe(PrettyPandas) 203 | .as_currency(subset=first_row_idx) 204 | .as_percent(subset=second_row_idx) 205 | .total(axis=1) 206 | ) 207 | 208 | .. image:: _static/Images/format_complex@2x.png 209 | :width: 370px 210 | 211 | For more info on Pandas indexing, read `Pandas Indexing`_ and `Pandas Advanced 212 | Indexing`_. 213 | 214 | .. _Pandas Indexing: http://pandas.pydata.org/pandas-docs/stable/indexing.html 215 | .. _Pandas Advanced Indexing: http://pandas.pydata.org/pandas-docs/stable/advanced.html 216 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PrettyPandas.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PrettyPandas.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PrettyPandas" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PrettyPandas" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | 194 | apidoc: 195 | sphinx-apidoc --module-first -e -f -o source ../prettypandas PrettyPandas 196 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PrettyPandas.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PrettyPandas.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # PrettyPandas documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Jan 19 12:59:36 2016. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | import shlex 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = '.rst' 44 | 45 | # The encoding of source files. 46 | #source_encoding = 'utf-8-sig' 47 | 48 | # The master toctree document. 49 | master_doc = 'index' 50 | 51 | # General information about the project. 52 | project = 'PrettyPandas' 53 | copyright = '2016, Henry Hammond' 54 | author = 'Henry Hammond' 55 | 56 | # The version info for the project you're documenting, acts as replacement for 57 | # |version| and |release|, also used in various other places throughout the 58 | # built documents. 59 | # 60 | # The short X.Y version. 61 | version = '0.0.4' 62 | # The full version, including alpha/beta/rc tags. 63 | release = '0.0.4' 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | # 68 | # This is also used if you do content translation via gettext catalogs. 69 | # Usually you set "language" from the command line for these cases. 70 | language = None 71 | 72 | # There are two options for replacing |today|: either, you set today to some 73 | # non-false value, then it is used: 74 | #today = '' 75 | # Else, today_fmt is used as the format for a strftime call. 76 | #today_fmt = '%B %d, %Y' 77 | 78 | # List of patterns, relative to source directory, that match files and 79 | # directories to ignore when looking for source files. 80 | exclude_patterns = [] 81 | 82 | # The reST default role (used for this markup: `text`) to use for all 83 | # documents. 84 | #default_role = None 85 | 86 | # If true, '()' will be appended to :func: etc. cross-reference text. 87 | #add_function_parentheses = True 88 | 89 | # If true, the current module name will be prepended to all description 90 | # unit titles (such as .. function::). 91 | #add_module_names = True 92 | 93 | # If true, sectionauthor and moduleauthor directives will be shown in the 94 | # output. They are ignored by default. 95 | #show_authors = False 96 | 97 | # The name of the Pygments (syntax highlighting) style to use. 98 | pygments_style = 'sphinx' 99 | 100 | # A list of ignored prefixes for module index sorting. 101 | #modindex_common_prefix = [] 102 | 103 | # If true, keep warnings as "system message" paragraphs in the built documents. 104 | #keep_warnings = False 105 | 106 | # If true, `todo` and `todoList` produce output, else they produce nothing. 107 | todo_include_todos = False 108 | 109 | 110 | # -- Options for HTML output ---------------------------------------------- 111 | 112 | # The theme to use for HTML and HTML Help pages. See the documentation for 113 | # a list of builtin themes. 114 | html_theme = 'sphinx_rtd_theme' 115 | # html_theme = 'alabaster' 116 | 117 | # Theme options are theme-specific and customize the look and feel of a theme 118 | # further. For a list of options available for each theme, see the 119 | # documentation. 120 | #html_theme_options = {} 121 | 122 | # Add any paths that contain custom themes here, relative to this directory. 123 | #html_theme_path = [] 124 | 125 | # The name for this set of Sphinx documents. If None, it defaults to 126 | # " v documentation". 127 | #html_title = None 128 | 129 | # A shorter title for the navigation bar. Default is the same as html_title. 130 | #html_short_title = None 131 | 132 | # The name of an image file (relative to this directory) to place at the top 133 | # of the sidebar. 134 | #html_logo = None 135 | 136 | # The name of an image file (within the static path) to use as favicon of the 137 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 138 | # pixels large. 139 | #html_favicon = None 140 | 141 | # Add any paths that contain custom static files (such as style sheets) here, 142 | # relative to this directory. They are copied after the builtin static files, 143 | # so a file named "default.css" will overwrite the builtin "default.css". 144 | html_static_path = ['_static'] 145 | 146 | # Add any extra paths that contain custom files (such as robots.txt or 147 | # .htaccess) here, relative to this directory. These files are copied 148 | # directly to the root of the documentation. 149 | #html_extra_path = [] 150 | 151 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 152 | # using the given strftime format. 153 | #html_last_updated_fmt = '%b %d, %Y' 154 | 155 | # If true, SmartyPants will be used to convert quotes and dashes to 156 | # typographically correct entities. 157 | #html_use_smartypants = True 158 | 159 | # Custom sidebar templates, maps document names to template names. 160 | #html_sidebars = {} 161 | 162 | # Additional templates that should be rendered to pages, maps page names to 163 | # template names. 164 | #html_additional_pages = {} 165 | 166 | # If false, no module index is generated. 167 | #html_domain_indices = True 168 | 169 | # If false, no index is generated. 170 | #html_use_index = True 171 | 172 | # If true, the index is split into individual pages for each letter. 173 | #html_split_index = False 174 | 175 | # If true, links to the reST sources are added to the pages. 176 | #html_show_sourcelink = True 177 | 178 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 179 | #html_show_sphinx = True 180 | 181 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 182 | #html_show_copyright = True 183 | 184 | # If true, an OpenSearch description file will be output, and all pages will 185 | # contain a tag referring to it. The value of this option must be the 186 | # base URL from which the finished HTML is served. 187 | #html_use_opensearch = '' 188 | 189 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 190 | #html_file_suffix = None 191 | 192 | # Language to be used for generating the HTML full-text search index. 193 | # Sphinx supports the following languages: 194 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 195 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' 196 | #html_search_language = 'en' 197 | 198 | # A dictionary with options for the search language support, empty by default. 199 | # Now only 'ja' uses this config value 200 | #html_search_options = {'type': 'default'} 201 | 202 | # The name of a javascript file (relative to the configuration directory) that 203 | # implements a search results scorer. If empty, the default will be used. 204 | #html_search_scorer = 'scorer.js' 205 | 206 | # Output file base name for HTML help builder. 207 | htmlhelp_basename = 'PrettyPandasdoc' 208 | 209 | # -- Options for LaTeX output --------------------------------------------- 210 | 211 | latex_elements = { 212 | # The paper size ('letterpaper' or 'a4paper'). 213 | #'papersize': 'letterpaper', 214 | 215 | # The font size ('10pt', '11pt' or '12pt'). 216 | #'pointsize': '10pt', 217 | 218 | # Additional stuff for the LaTeX preamble. 219 | #'preamble': '', 220 | 221 | # Latex figure (float) alignment 222 | #'figure_align': 'htbp', 223 | } 224 | 225 | # Grouping the document tree into LaTeX files. List of tuples 226 | # (source start file, target name, title, 227 | # author, documentclass [howto, manual, or own class]). 228 | latex_documents = [ 229 | (master_doc, 'PrettyPandas.tex', 'PrettyPandas Documentation', 230 | 'Henry Hammond', 'manual'), 231 | ] 232 | 233 | # The name of an image file (relative to this directory) to place at the top of 234 | # the title page. 235 | #latex_logo = None 236 | 237 | # For "manual" documents, if this is true, then toplevel headings are parts, 238 | # not chapters. 239 | #latex_use_parts = False 240 | 241 | # If true, show page references after internal links. 242 | #latex_show_pagerefs = False 243 | 244 | # If true, show URL addresses after external links. 245 | #latex_show_urls = False 246 | 247 | # Documents to append as an appendix to all manuals. 248 | #latex_appendices = [] 249 | 250 | # If false, no module index is generated. 251 | #latex_domain_indices = True 252 | 253 | 254 | # -- Options for manual page output --------------------------------------- 255 | 256 | # One entry per manual page. List of tuples 257 | # (source start file, name, description, authors, manual section). 258 | man_pages = [ 259 | (master_doc, 'prettypandas', 'PrettyPandas Documentation', 260 | [author], 1) 261 | ] 262 | 263 | # If true, show URL addresses after external links. 264 | #man_show_urls = False 265 | 266 | 267 | # -- Options for Texinfo output ------------------------------------------- 268 | 269 | # Grouping the document tree into Texinfo files. List of tuples 270 | # (source start file, target name, title, author, 271 | # dir menu entry, description, category) 272 | texinfo_documents = [ 273 | (master_doc, 'PrettyPandas', 'PrettyPandas Documentation', 274 | author, 'PrettyPandas', 'One line description of project.', 275 | 'Miscellaneous'), 276 | ] 277 | 278 | # Documents to append as an appendix to all manuals. 279 | #texinfo_appendices = [] 280 | 281 | # If false, no module index is generated. 282 | #texinfo_domain_indices = True 283 | 284 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 285 | #texinfo_show_urls = 'footnote' 286 | 287 | # If true, do not generate a @detailmenu in the "Top" node's menu. 288 | #texinfo_no_detailmenu = False 289 | -------------------------------------------------------------------------------- /prettypandas/summarizer.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from operator import methodcaller 4 | import pandas as pd 5 | from .formatters import as_percent, as_currency, as_unit, LOCALE_OBJ 6 | 7 | 8 | def _axis_is_rows(axis): 9 | return axis == 0 or axis == 'rows' 10 | 11 | 12 | def _axis_is_cols(axis): 13 | return axis == 1 or axis == 'columns' or axis == 'index' 14 | 15 | 16 | class Aggregate(object): 17 | """Aggreagte 18 | 19 | Wrapper to calculate aggregate row on datafame. 20 | 21 | :param title: 22 | Aggregate row title 23 | :param func: 24 | Function to be passed to DataFrame.agg 25 | :param subset: 26 | Subset of DataFrame to compute aggregate on 27 | :param axis: 28 | Pandas axis to compute over 29 | 30 | :param args: 31 | Positionsal arguments to DataFrame.agg 32 | :param kwargs: 33 | Keyword arguments to DataFrame.agg 34 | """ 35 | 36 | def __init__( 37 | self, 38 | title, 39 | func, 40 | subset=None, 41 | axis=0, 42 | *args, 43 | **kwargs): 44 | 45 | self.title = title 46 | self.subset = subset 47 | self.axis = axis 48 | 49 | self.func = func 50 | self.args = args 51 | self.kwargs = kwargs 52 | 53 | def apply(self, df): 54 | """Compute aggregate over DataFrame""" 55 | 56 | if self.subset: 57 | if _axis_is_rows(self.axis): 58 | df = df[self.subset] 59 | if _axis_is_cols(self.axis): 60 | df = df.loc[self.subset] 61 | 62 | result = df.agg(self.func, axis=self.axis, *self.args, **self.kwargs) 63 | result.name = self.title 64 | return result 65 | 66 | 67 | class Formatter(object): 68 | """Formatter 69 | 70 | Wrapper to apply formatting to datafame. 71 | 72 | :param formatter: 73 | Function to be passed to Pandas Styler.format 74 | :param args: 75 | Positionsal arguments to Styler.format 76 | :param kwargs: 77 | Keyword arguments to Styler.format 78 | """ 79 | 80 | def __init__(self, formatter, args, kwargs): 81 | self.formatter = formatter 82 | self.args = args 83 | self.kwargs = kwargs 84 | 85 | def apply(self, styler): 86 | """Apply Summary over Pandas Styler""" 87 | return styler.format(self.formatter, *self.args, **self.kwargs) 88 | 89 | 90 | class PrettyPandas(object): 91 | """PrettyPandas 92 | 93 | Parameters 94 | ---------- 95 | :param data: DataFrame. 96 | :param summary_rows: 97 | list of Aggregate objects to be appended as a summary. 98 | :param summary_cols: 99 | list of Aggregate objects to be appended as a summary. 100 | :param formatters: 101 | List of Formatter objects to format. 102 | """ 103 | 104 | def __init__(self, 105 | data, 106 | summary_rows=None, 107 | summary_cols=None, 108 | formatters=None, 109 | *args, 110 | **kwargs): 111 | 112 | self.data = data 113 | self.summary_rows = summary_rows or [] 114 | self.summary_cols = summary_cols or [] 115 | self.formatters = formatters or [] 116 | 117 | def _copy(self): 118 | return self.__class__( 119 | self.data, 120 | summary_rows=self.summary_rows[:], 121 | summary_cols=self.summary_cols[:], 122 | formatters=self.formatters[:], 123 | ) 124 | 125 | def _add_formatter(self, formatter): 126 | new = self._copy() 127 | new.formatters += [formatter] 128 | return new 129 | 130 | def _add_summary(self, agg): 131 | new = self._copy() 132 | 133 | if _axis_is_rows(agg.axis): 134 | new.summary_rows += [agg] 135 | elif _axis_is_cols(agg.axis): 136 | new.summary_cols += [agg] 137 | else: 138 | raise ValueError("Invalid axis supplied.") 139 | 140 | return new 141 | 142 | def _cleaned_aggregates(self, summaries): 143 | titles = set() 144 | for agg in summaries: 145 | title = agg.title 146 | i = 1 147 | while agg.title in titles: 148 | agg.title = "{}_{}".format(title, i) 149 | i += 1 150 | 151 | titles.add(agg.title) 152 | yield agg 153 | 154 | @property 155 | def _cleaned_summary_rows(self): 156 | return list(self._cleaned_aggregates(self.summary_rows)) 157 | 158 | @property 159 | def _cleaned_summary_cols(self): 160 | return list(self._cleaned_aggregates(self.summary_cols)) 161 | 162 | def _apply_summaries(self): 163 | """Add all summary rows and columns.""" 164 | 165 | def as_frame(r): 166 | if isinstance(r, pd.Series): 167 | return r.to_frame() 168 | else: 169 | return r 170 | 171 | df = self.data 172 | 173 | if df.index.nlevels > 1: 174 | raise ValueError( 175 | "You cannot currently have both summary rows and columns on a " 176 | "MultiIndex." 177 | ) 178 | 179 | _df = df 180 | if self.summary_rows: 181 | rows = pd.concat([agg.apply(_df) 182 | for agg in self._cleaned_summary_rows], axis=1).T 183 | df = pd.concat([df, as_frame(rows)], axis=0) 184 | 185 | if self.summary_cols: 186 | cols = pd.concat([agg.apply(_df) 187 | for agg in self._cleaned_summary_cols], axis=1) 188 | df = pd.concat([df, as_frame(cols)], axis=1) 189 | 190 | return df 191 | 192 | @property 193 | def frame(self): 194 | """Add summaries and convert back to DataFrame""" 195 | return self._apply_summaries() 196 | 197 | def to_frame(self): 198 | """Add summaries and convert back to DataFrame""" 199 | return self.frame 200 | 201 | @property 202 | def style(self): 203 | """Add summaries and convert to Pandas Styler""" 204 | row_titles = [a.title for a in self._cleaned_summary_rows] 205 | col_titles = [a.title for a in self._cleaned_summary_cols] 206 | row_ix = pd.IndexSlice[row_titles, :] 207 | col_ix = pd.IndexSlice[:, col_titles] 208 | 209 | def handle_na(df): 210 | df.loc[col_ix] = df.loc[col_ix].fillna('') 211 | df.loc[row_ix] = df.loc[row_ix].fillna('') 212 | return df 213 | 214 | styler = ( 215 | self 216 | .frame 217 | .pipe(handle_na) 218 | .style 219 | .applymap(lambda r: 'font-weight: 900', subset=row_ix) 220 | .applymap(lambda r: 'font-weight: 900', subset=col_ix) 221 | ) 222 | 223 | for formatter in self.formatters: 224 | styler = formatter.apply(styler) 225 | 226 | return styler 227 | 228 | def render(self): 229 | return self.style.render() 230 | 231 | def _repr_html_(self): 232 | return self.style._repr_html_() 233 | 234 | def __str__(self): 235 | return str(self.frame) 236 | 237 | def __repr__(self): 238 | return str(self.frame) 239 | 240 | def summary(self, 241 | func=methodcaller('sum'), 242 | title='Total', 243 | axis=0, 244 | subset=None, 245 | *args, 246 | **kwargs): 247 | """Add multiple summary rows or columns to the dataframe. 248 | 249 | Parameters 250 | ---------- 251 | :param func: function to be used for a summary. 252 | :param titles: Title for this summary column. 253 | :param axis: 254 | Same as numpy and pandas axis argument. A value of None will cause 255 | the summary to be applied to both rows and columns. 256 | :param args: Positional arguments passed to all the functions. 257 | :param kwargs: Keyword arguments passed to all the functions. 258 | 259 | The results of summary can be chained together. 260 | """ 261 | 262 | if axis is None: 263 | return ( 264 | self 265 | .summary( 266 | func=func, 267 | title=title, 268 | axis=0, 269 | subset=subset, 270 | *args, 271 | **kwargs 272 | ) 273 | .summary( 274 | func=func, 275 | title=title, 276 | axis=1, 277 | subset=subset, 278 | *args, 279 | **kwargs 280 | ) 281 | ) 282 | else: 283 | agg = Aggregate(title, func, subset=subset, 284 | axis=axis, *args, **kwargs) 285 | return self._add_summary(agg) 286 | 287 | def multi_summary( 288 | self, 289 | funcs, 290 | titles, 291 | axis=0, 292 | *args, 293 | **kwargs): 294 | 295 | new = self 296 | for f, t in zip(funcs, titles): 297 | new = new.summary(func=f, title=t, axis=axis, *args, **kwargs) 298 | return new 299 | 300 | def total(self, title="Total", **kwargs): 301 | """Add a total summary to this table. 302 | 303 | :param title: Title to be displayed. 304 | """ 305 | return self.summary(methodcaller('sum'), title, **kwargs) 306 | 307 | def average(self, title="Average", **kwargs): 308 | """Add a mean summary to this table. 309 | 310 | :param title: Title to be displayed. 311 | """ 312 | return self.summary(methodcaller('mean'), title, **kwargs) 313 | 314 | def median(self, title="Median", **kwargs): 315 | """Add a median summary to this table. 316 | 317 | :param title: Title to be displayed. 318 | """ 319 | return self.summary(methodcaller('median'), title, **kwargs) 320 | 321 | def max(self, title="Maximum", **kwargs): 322 | """Add a maximum summary to this table. 323 | 324 | :param title: Title to be displayed. 325 | """ 326 | return self.summary(methodcaller('max'), title, **kwargs) 327 | 328 | def min(self, title="Minimum", **kwargs): 329 | """Add a minimum summary to this table. 330 | 331 | :param title: Title to be displayed. 332 | """ 333 | return self.summary(methodcaller('min'), title, **kwargs) 334 | 335 | def as_percent(self, precision=2, *args, **kwargs): 336 | """Format subset as percentages 337 | 338 | :param precision: Decimal precision 339 | :param subset: Pandas subset 340 | """ 341 | f = Formatter(as_percent(precision), args, kwargs) 342 | return self._add_formatter(f) 343 | 344 | def as_currency(self, currency='USD', locale=LOCALE_OBJ, *args, **kwargs): 345 | """Format subset as currency 346 | 347 | :param currency: Currency 348 | :param locale: Babel locale for currency formatting 349 | :param subset: Pandas subset 350 | """ 351 | f = Formatter( 352 | as_currency(currency=currency, locale=locale), 353 | args, 354 | kwargs 355 | ) 356 | return self._add_formatter(f) 357 | 358 | def as_unit(self, unit, location='suffix', *args, **kwargs): 359 | """Format subset as with units 360 | 361 | :param unit: string to use as unit 362 | :param location: prefix or suffix 363 | :param subset: Pandas subset 364 | """ 365 | f = Formatter( 366 | as_unit(unit, location=location), 367 | args, 368 | kwargs 369 | ) 370 | return self._add_formatter(f) 371 | --------------------------------------------------------------------------------