├── 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 | 
4 | [](http://prettypandas.readthedocs.org/en/latest/?badge=latest)
5 | 
6 | 
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 |
--------------------------------------------------------------------------------