From 9da99bff38d290c80d74cfe28874a38bc4fd60a0 Mon Sep 17 00:00:00 2001 From: Martin Babka Date: Tue, 22 May 2018 22:25:19 +0200 Subject: [PATCH 01/19] Fix nonzero of a SparseArray The nonzero operation returned the nonzero locations of the underlying index. However we need to get the nonzero locations in the real array. For this operation to be faster an inverse index structure would be beneficial or it could be implemented using binary search. ```python sa = pd.SparseArray([float('nan'), float('nan'), 1, 0, 0, 2, 0, 0, 0, 3, 0, 0]) ``` returned `0, 3, 7`. The index is shifted by two because of the two first `NaN`s and that's why the `0, 3, 7` are returned. The correct result would be `2, 5, 9` and is found in the method. --- pandas/core/sparse/array.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pandas/core/sparse/array.py b/pandas/core/sparse/array.py index 5532d7522cd2d..8fe25e21967a7 100644 --- a/pandas/core/sparse/array.py +++ b/pandas/core/sparse/array.py @@ -735,6 +735,17 @@ def value_counts(self, dropna=True): result = pd.Series(counts, index=keys) return result + def nonzero(self): + nonzero_locations = super(SparseArray, self).nonzero()[0].astype(int32) + real_nonzero_locations = np.zeros(len(nonzero_locations), dtype=int32) + real_location = 0 + for i_loc, location in enumerate(nonzero_locations): + # TODO: One could vectorize the operation and/or implement binary search. + while self.sp_index.lookup(real_location) != location: + real_location += 1 + real_nonzero_locations[i_loc] = real_location + real_location += 1 + return real_nonzero_locations.reshape(1, len(real_nonzero_locations)) def _maybe_to_dense(obj): """ try to convert to dense """ From 349b84716a1d33d67acdc28c3139d5d8baabba25 Mon Sep 17 00:00:00 2001 From: Martin Babka Date: Wed, 7 Nov 2018 21:42:05 +0100 Subject: [PATCH 02/19] Fix PEP8 line length --- pandas/core/sparse/array.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/core/sparse/array.py b/pandas/core/sparse/array.py index 8fe25e21967a7..f4c94b6d8223e 100644 --- a/pandas/core/sparse/array.py +++ b/pandas/core/sparse/array.py @@ -740,7 +740,8 @@ def nonzero(self): real_nonzero_locations = np.zeros(len(nonzero_locations), dtype=int32) real_location = 0 for i_loc, location in enumerate(nonzero_locations): - # TODO: One could vectorize the operation and/or implement binary search. + # TODO: One could vectorize the operation and/or implement binary + # search. while self.sp_index.lookup(real_location) != location: real_location += 1 real_nonzero_locations[i_loc] = real_location From eb1c7065e0258ce0f02d57f972948f8c932cd434 Mon Sep 17 00:00:00 2001 From: Martin Babka Date: Wed, 7 Nov 2018 21:44:35 +0100 Subject: [PATCH 03/19] Fix np.int32 imports --- pandas/core/sparse/array.py | 53 ++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/pandas/core/sparse/array.py b/pandas/core/sparse/array.py index f4c94b6d8223e..bef087eeccd9b 100644 --- a/pandas/core/sparse/array.py +++ b/pandas/core/sparse/array.py @@ -2,19 +2,27 @@ SparseArray data structure """ from __future__ import division -# pylint: disable=E1101,E1103,W0231 -import numpy as np import warnings -import pandas as pd -from pandas.core.base import PandasObject, IndexOpsMixin +import numpy as np +import pandas as pd +import pandas._libs.lib as lib +import pandas._libs.sparse as splib +import pandas.core.algorithms as algos +import pandas.core.ops as ops +import pandas.io.formats.printing as printing from pandas import compat +from pandas._libs import index as libindex +from pandas._libs.sparse import SparseIndex, BlockIndex, IntIndex from pandas.compat import range, PYPY from pandas.compat.numpy import function as nv - -from pandas.core.dtypes.generic import ABCSparseSeries +from pandas.core.base import PandasObject, IndexOpsMixin +from pandas.core.dtypes.cast import ( + maybe_convert_platform, maybe_promote, + astype_nansafe, find_common_type, infer_dtype_from_scalar, + construct_1d_arraylike_from_scalar) from pandas.core.dtypes.common import ( _ensure_platform_int, is_float, is_integer, @@ -24,21 +32,12 @@ is_list_like, is_string_dtype, is_scalar, is_dtype_equal) -from pandas.core.dtypes.cast import ( - maybe_convert_platform, maybe_promote, - astype_nansafe, find_common_type, infer_dtype_from_scalar, - construct_1d_arraylike_from_scalar) +from pandas.core.dtypes.generic import ABCSparseSeries from pandas.core.dtypes.missing import isna, notna, na_value_for_dtype - -import pandas._libs.sparse as splib -import pandas._libs.lib as lib -from pandas._libs.sparse import SparseIndex, BlockIndex, IntIndex -from pandas._libs import index as libindex -import pandas.core.algorithms as algos -import pandas.core.ops as ops -import pandas.io.formats.printing as printing -from pandas.util._decorators import Appender from pandas.core.indexes.base import _index_shared_docs +from pandas.util._decorators import Appender + +# pylint: disable=E1101,E1103,W0231 _sparray_doc_kwargs = dict(klass='SparseArray') @@ -316,6 +315,7 @@ def __unicode__(self): def disable(self, other): raise NotImplementedError('inplace binary ops not supported') + # Inplace operators __iadd__ = disable __isub__ = disable @@ -492,8 +492,8 @@ def take(self, indices, axis=0, allow_fill=True, indexer = indexer[mask] new_values = self.sp_values.take(locs[mask]) else: - indexer = np.empty(shape=(0, ), dtype=np.int32) - new_values = np.empty(shape=(0, ), dtype=self.sp_values.dtype) + indexer = np.empty(shape=(0,), dtype=np.int32) + new_values = np.empty(shape=(0,), dtype=self.sp_values.dtype) sp_index = _make_index(len(indices), indexer, kind=self.sp_index) return self._simple_new(new_values, sp_index, self.fill_value) @@ -736,8 +736,13 @@ def value_counts(self, dropna=True): return result def nonzero(self): - nonzero_locations = super(SparseArray, self).nonzero()[0].astype(int32) - real_nonzero_locations = np.zeros(len(nonzero_locations), dtype=int32) + nonzero_locations = super(SparseArray, self) \ + .nonzero()[0] \ + .astype(np.int32) + real_nonzero_locations = np.zeros( + len(nonzero_locations), + dtype=np.int32 + ) real_location = 0 for i_loc, location in enumerate(nonzero_locations): # TODO: One could vectorize the operation and/or implement binary @@ -748,6 +753,7 @@ def nonzero(self): real_location += 1 return real_nonzero_locations.reshape(1, len(real_nonzero_locations)) + def _maybe_to_dense(obj): """ try to convert to dense """ if hasattr(obj, 'to_dense'): @@ -842,7 +848,6 @@ def make_sparse(arr, kind='block', fill_value=None): def _make_index(length, indices, kind): - if kind == 'block' or isinstance(kind, BlockIndex): locs, lens = splib.get_blocks(indices) index = BlockIndex(length, locs, lens) From 41f07f5293befeff065f6962577fdee1fa88c095 Mon Sep 17 00:00:00 2001 From: Martin Babka Date: Wed, 7 Nov 2018 22:14:49 +0100 Subject: [PATCH 04/19] Add nonzero tests --- pandas/tests/sparse/test_indexing.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/tests/sparse/test_indexing.py b/pandas/tests/sparse/test_indexing.py index 37a287af71451..eb9e02491f69a 100644 --- a/pandas/tests/sparse/test_indexing.py +++ b/pandas/tests/sparse/test_indexing.py @@ -448,6 +448,12 @@ def tests_indexing_with_sparse(self): with tm.assert_raises_regex(ValueError, msg): s.iloc[indexer] + def test_nonzero(self): + sa = pd.SparseArray( + [float('nan'), float('nan'), 1, 0, 0, 2, 0, 0, 0, 3, 0, 0]) + sa = sa.nonzero() + tm.assert_numpy_array_equal(np.array([2, 5, 9], sa)) + class TestSparseSeriesMultiIndexing(TestSparseSeriesIndexing): From 02dd63d73b98bded63960079b38722ef977a432d Mon Sep 17 00:00:00 2001 From: Martin Babka Date: Thu, 8 Nov 2018 07:43:48 +0100 Subject: [PATCH 05/19] Merge pandas-dev/master --- .circleci/config.yml | 42 + .coveragerc | 27 - .gitignore | 2 + .pep8speaks.yml | 14 +- .travis.yml | 52 +- MANIFEST.in | 34 +- Makefile | 3 +- README.md | 8 +- appveyor.yml | 89 - asv_bench/benchmarks/algorithms.py | 17 +- asv_bench/benchmarks/attrs_caching.py | 9 +- asv_bench/benchmarks/binary_ops.py | 15 +- asv_bench/benchmarks/categoricals.py | 68 +- asv_bench/benchmarks/ctors.py | 11 +- asv_bench/benchmarks/eval.py | 9 +- asv_bench/benchmarks/frame_ctor.py | 13 +- asv_bench/benchmarks/frame_methods.py | 76 +- asv_bench/benchmarks/gil.py | 11 +- asv_bench/benchmarks/groupby.py | 68 +- asv_bench/benchmarks/index_object.py | 17 +- asv_bench/benchmarks/indexing.py | 148 +- asv_bench/benchmarks/indexing_engines.py | 64 + asv_bench/benchmarks/inference.py | 9 +- asv_bench/benchmarks/io/csv.py | 74 +- asv_bench/benchmarks/io/excel.py | 6 +- asv_bench/benchmarks/io/hdf.py | 10 +- asv_bench/benchmarks/io/json.py | 8 +- asv_bench/benchmarks/io/msgpack.py | 7 +- asv_bench/benchmarks/io/pickle.py | 7 +- asv_bench/benchmarks/io/sas.py | 1 - asv_bench/benchmarks/io/sql.py | 11 +- asv_bench/benchmarks/io/stata.py | 6 +- asv_bench/benchmarks/join_merge.py | 28 +- asv_bench/benchmarks/multiindex_object.py | 17 +- asv_bench/benchmarks/offset.py | 7 - asv_bench/benchmarks/pandas_vb_common.py | 5 +- asv_bench/benchmarks/panel_ctor.py | 13 +- asv_bench/benchmarks/panel_methods.py | 7 +- asv_bench/benchmarks/period.py | 16 +- asv_bench/benchmarks/plotting.py | 11 +- asv_bench/benchmarks/reindex.py | 16 +- asv_bench/benchmarks/replace.py | 8 +- asv_bench/benchmarks/reshape.py | 53 +- asv_bench/benchmarks/rolling.py | 5 +- asv_bench/benchmarks/series_methods.py | 77 +- asv_bench/benchmarks/sparse.py | 16 +- asv_bench/benchmarks/stat_ops.py | 15 +- asv_bench/benchmarks/strings.py | 41 +- asv_bench/benchmarks/timedelta.py | 11 - asv_bench/benchmarks/timeseries.py | 49 +- asv_bench/benchmarks/timestamp.py | 6 - asv_bench/vbench_to_asv.py | 163 - azure-pipelines.yml | 25 + ci/azure/linux.yml | 81 + ci/azure/macos.yml | 68 + ci/azure/windows-py27.yml | 58 + ci/azure/windows.yml | 49 + ci/build_docs.sh | 18 - ci/check_imports.py | 35 - ci/{ => circle}/install_circle.sh | 19 +- ci/circle/run_circle.sh | 9 + ci/{ => circle}/show_circle.sh | 0 ci/code_checks.sh | 165 + .../azure-27-compat.yaml} | 15 +- ci/deps/azure-36-locale_slow.yaml | 36 + .../azure-37-locale.yaml} | 4 +- .../azure-macos-35.yaml} | 9 +- .../azure-windows-27.yaml} | 10 +- .../azure-windows-36.yaml} | 10 +- ci/{ => deps}/circle-36-locale.yaml | 4 +- ci/{ => deps}/travis-27-locale.yaml | 9 +- ci/{ => deps}/travis-27.yaml | 16 +- ci/{ => deps}/travis-36-doc.yaml | 8 +- ci/{ => deps}/travis-36-slow.yaml | 3 +- ci/{ => deps}/travis-36.yaml | 15 +- .../travis-37-numpydev.yaml} | 5 +- .../travis-37.yaml} | 11 +- ci/environment-dev.yaml | 8 +- ci/incremental/build.cmd | 10 + ci/incremental/build.sh | 18 + ci/incremental/install_miniconda.sh | 19 + ci/incremental/setup_conda_environment.cmd | 21 + ci/incremental/setup_conda_environment.sh | 52 + ci/install.ps1 | 92 - ci/install_db_circle.sh | 8 - ci/lint.sh | 181 - ci/print_skipped.py | 6 +- ci/print_versions.py | 3 +- ci/requirements-optional-conda.txt | 17 +- ci/requirements-optional-pip.txt | 17 +- ci/requirements_dev.txt | 8 +- ci/run_circle.sh | 9 - ci/script_multi.sh | 12 +- ci/script_single.sh | 14 +- circle.yml | 38 - conda.recipe/meta.yaml | 18 +- doc/cheatsheet/Pandas_Cheat_Sheet_JA.pdf | Bin 0 -> 206153 bytes doc/cheatsheet/Pandas_Cheat_Sheet_JA.pptx | Bin 0 -> 76495 bytes doc/make.py | 27 +- doc/source/advanced.rst | 169 +- doc/source/api.rst | 117 +- doc/source/basics.rst | 103 +- doc/source/categorical.rst | 14 +- doc/source/comparison_with_r.rst | 10 +- doc/source/comparison_with_sas.rst | 18 +- doc/source/comparison_with_sql.rst | 4 +- doc/source/computation.rst | 61 +- doc/source/conf.py | 37 +- doc/source/contributing.rst | 298 +- doc/source/contributing_docstring.rst | 88 +- doc/source/cookbook.rst | 115 +- doc/source/developer.rst | 14 +- doc/source/dsintro.rst | 42 +- doc/source/ecosystem.rst | 205 +- doc/source/enhancingperf.rst | 46 +- doc/source/extending.rst | 108 +- doc/source/gotchas.rst | 4 +- doc/source/groupby.rst | 84 +- doc/source/index.rst.template | 16 +- doc/source/indexing.rst | 84 +- doc/source/install.rst | 46 +- doc/source/internals.rst | 40 +- doc/source/io.rst | 570 +- doc/source/merging.rst | 130 +- doc/source/missing_data.rst | 4 +- doc/source/names_wordlist.txt | 1652 +++++ doc/source/options.rst | 12 +- doc/source/overview.rst | 30 +- doc/source/release.rst | 217 +- doc/source/reshaping.rst | 50 +- doc/source/sparse.rst | 26 +- doc/source/spelling_wordlist.txt | 920 +++ doc/source/style.ipynb | 5 +- doc/source/text.rst | 24 +- doc/source/timedeltas.rst | 7 + doc/source/timeseries.rst | 274 +- doc/source/tutorials.rst | 168 +- doc/source/visualization.rst | 8 +- doc/source/whatsnew.rst | 6 + doc/source/whatsnew/v0.10.0.txt | 9 +- doc/source/whatsnew/v0.10.1.txt | 8 +- doc/source/whatsnew/v0.11.0.txt | 8 +- doc/source/whatsnew/v0.12.0.txt | 10 +- doc/source/whatsnew/v0.13.0.txt | 4 +- doc/source/whatsnew/v0.13.1.txt | 3 +- doc/source/whatsnew/v0.14.0.txt | 42 +- doc/source/whatsnew/v0.14.1.txt | 28 +- doc/source/whatsnew/v0.15.0.txt | 26 +- doc/source/whatsnew/v0.15.1.txt | 4 +- doc/source/whatsnew/v0.15.2.txt | 12 +- doc/source/whatsnew/v0.16.0.txt | 12 +- doc/source/whatsnew/v0.16.1.txt | 10 +- doc/source/whatsnew/v0.16.2.txt | 4 +- doc/source/whatsnew/v0.17.0.txt | 6 +- doc/source/whatsnew/v0.17.1.txt | 8 +- doc/source/whatsnew/v0.18.0.txt | 18 +- doc/source/whatsnew/v0.18.1.txt | 10 +- doc/source/whatsnew/v0.19.0.txt | 6 +- doc/source/whatsnew/v0.19.1.txt | 2 +- doc/source/whatsnew/v0.20.0.txt | 14 +- doc/source/whatsnew/v0.21.0.txt | 2 +- doc/source/whatsnew/v0.21.1.txt | 6 +- doc/source/whatsnew/v0.23.0.txt | 25 +- doc/source/whatsnew/v0.23.1.txt | 159 +- doc/source/whatsnew/v0.23.2.txt | 108 + doc/source/whatsnew/v0.23.3.txt | 7 + doc/source/whatsnew/v0.23.4.txt | 37 + doc/source/whatsnew/v0.23.5.txt | 54 + doc/source/whatsnew/v0.24.0.txt | 1318 +++- doc/source/whatsnew/v0.6.0.txt | 2 +- doc/source/whatsnew/v0.8.0.txt | 8 +- doc/source/whatsnew/v0.9.1.txt | 2 +- pandas/__init__.py | 30 +- pandas/_libs/__init__.py | 8 +- pandas/_libs/algos.pxd | 7 +- pandas/_libs/algos.pyx | 602 +- pandas/_libs/algos_common_helper.pxi.in | 497 +- pandas/_libs/algos_rank_helper.pxi.in | 72 +- pandas/_libs/algos_take_helper.pxi.in | 60 +- pandas/_libs/groupby.pyx | 49 +- pandas/_libs/groupby_helper.pxi.in | 422 +- pandas/_libs/hashing.pyx | 52 +- pandas/_libs/hashtable.pyx | 10 +- pandas/_libs/hashtable_class_helper.pxi.in | 437 +- pandas/_libs/hashtable_func_helper.pxi.in | 84 +- pandas/_libs/index.pyx | 104 +- pandas/_libs/index_class_helper.pxi.in | 35 +- pandas/_libs/indexing.pyx | 7 +- pandas/_libs/internals.pyx | 106 +- pandas/_libs/interval.pyx | 145 +- pandas/_libs/intervaltree.pxi.in | 6 + pandas/_libs/join.pyx | 784 ++- pandas/_libs/join_func_helper.pxi.in | 374 -- pandas/_libs/join_helper.pxi.in | 408 -- pandas/_libs/khash.pxd | 1 - pandas/_libs/lib.pyx | 1730 ++++- pandas/_libs/missing.pxd | 9 +- pandas/_libs/missing.pyx | 80 +- pandas/_libs/ops.pyx | 63 +- pandas/_libs/parsers.pyx | 117 +- pandas/_libs/properties.pyx | 4 +- pandas/_libs/reduction.pyx | 21 +- pandas/_libs/reshape.pyx | 100 +- pandas/_libs/reshape_helper.pxi.in | 81 - pandas/_libs/skiplist.pxd | 5 +- pandas/_libs/skiplist.pyx | 18 +- pandas/_libs/sparse.pyx | 78 +- pandas/_libs/sparse_op_helper.pxi.in | 90 +- pandas/_libs/src/compat_helper.h | 2 +- pandas/_libs/src/datetime/np_datetime.c | 959 --- pandas/_libs/src/datetime/np_datetime.h | 110 - .../_libs/src/datetime/np_datetime_strings.c | 882 --- pandas/_libs/src/headers/cmath | 27 +- pandas/_libs/src/inference.pyx | 1599 ----- .../_libs/src/{helper.h => inline_helper.h} | 6 +- pandas/_libs/src/klib/khash.h | 13 +- pandas/_libs/src/klib/khash_python.h | 26 +- pandas/_libs/src/numpy_helper.h | 56 - pandas/_libs/src/parse_helper.h | 1 + pandas/_libs/src/parser/io.c | 6 +- pandas/_libs/src/parser/tokenizer.c | 14 +- pandas/_libs/src/parser/tokenizer.h | 12 +- pandas/_libs/src/period_helper.c | 601 -- pandas/_libs/src/period_helper.h | 112 - pandas/_libs/src/skiplist.h | 13 +- pandas/_libs/src/ujson/python/objToJSON.c | 48 +- pandas/_libs/src/util.pxd | 163 - pandas/_libs/testing.pyx | 7 + pandas/_libs/tslib.pyx | 410 +- pandas/_libs/tslibs/__init__.py | 9 +- pandas/_libs/tslibs/ccalendar.pxd | 3 +- pandas/_libs/tslibs/ccalendar.pyx | 9 +- pandas/_libs/tslibs/conversion.pxd | 7 +- pandas/_libs/tslibs/conversion.pyx | 627 +- pandas/_libs/tslibs/fields.pyx | 62 +- pandas/_libs/tslibs/frequencies.pxd | 1 - pandas/_libs/tslibs/frequencies.pyx | 26 +- pandas/_libs/tslibs/nattype.pxd | 2 +- pandas/_libs/tslibs/nattype.pyx | 127 +- pandas/_libs/tslibs/np_datetime.pxd | 62 +- pandas/_libs/tslibs/np_datetime.pyx | 69 +- pandas/_libs/tslibs/offsets.pxd | 3 + pandas/_libs/tslibs/offsets.pyx | 229 +- pandas/_libs/tslibs/parsing.pyx | 128 +- pandas/_libs/tslibs/period.pyx | 1070 ++- pandas/_libs/tslibs/resolution.pyx | 409 +- .../_libs/tslibs/src/datetime/np_datetime.h | 80 + .../src/datetime/np_datetime_strings.h | 24 +- pandas/_libs/tslibs/strptime.pyx | 154 +- pandas/_libs/tslibs/timedeltas.pxd | 9 +- pandas/_libs/tslibs/timedeltas.pyx | 312 +- pandas/_libs/tslibs/timestamps.pxd | 7 +- pandas/_libs/tslibs/timestamps.pyx | 329 +- pandas/_libs/tslibs/timezones.pxd | 3 +- pandas/_libs/tslibs/timezones.pyx | 32 +- pandas/_libs/tslibs/util.pxd | 228 + pandas/_libs/util.pxd | 114 + pandas/_libs/window.pyx | 590 +- pandas/_libs/writers.pyx | 41 +- pandas/_version.py | 1 + pandas/api/extensions/__init__.py | 7 +- pandas/compat/__init__.py | 23 +- pandas/compat/chainmap_impl.py | 9 +- pandas/compat/numpy/__init__.py | 32 +- pandas/compat/numpy/function.py | 31 +- pandas/compat/pickle_compat.py | 33 +- pandas/computation/expressions.py | 15 - pandas/conftest.py | 401 +- pandas/core/accessor.py | 35 + pandas/core/algorithms.py | 215 +- pandas/core/api.py | 36 +- pandas/core/apply.py | 22 +- pandas/core/arrays/__init__.py | 11 +- pandas/core/arrays/base.py | 293 +- pandas/core/arrays/categorical.py | 412 +- pandas/core/arrays/datetimelike.py | 966 +++ pandas/core/arrays/datetimes.py | 1561 +++++ pandas/core/arrays/integer.py | 676 ++ pandas/core/arrays/interval.py | 1124 ++++ pandas/core/arrays/period.py | 1086 +++ pandas/core/arrays/sparse.py | 1924 ++++++ pandas/core/arrays/timedeltas.py | 415 ++ pandas/core/base.py | 199 +- pandas/core/common.py | 354 +- pandas/core/computation/align.py | 7 +- pandas/core/computation/api.py | 11 - pandas/core/computation/check.py | 4 +- pandas/core/computation/common.py | 4 +- pandas/core/computation/engines.py | 8 +- pandas/core/computation/eval.py | 12 +- pandas/core/computation/expr.py | 20 +- pandas/core/computation/expressions.py | 11 +- pandas/core/computation/ops.py | 12 +- pandas/core/computation/pytables.py | 20 +- pandas/core/computation/scope.py | 12 +- pandas/core/config.py | 4 +- pandas/core/datetools.py | 55 - pandas/core/dtypes/api.py | 92 +- pandas/core/dtypes/base.py | 119 +- pandas/core/dtypes/cast.py | 264 +- pandas/core/dtypes/common.py | 224 +- pandas/core/dtypes/concat.py | 111 +- pandas/core/dtypes/dtypes.py | 226 +- pandas/core/dtypes/generic.py | 7 +- pandas/core/dtypes/inference.py | 35 +- pandas/core/dtypes/missing.py | 73 +- pandas/core/frame.py | 2066 +++--- pandas/core/generic.py | 2370 +++++-- pandas/core/groupby/__init__.py | 8 +- pandas/core/groupby/base.py | 168 + pandas/core/groupby/categorical.py | 100 + pandas/core/groupby/generic.py | 1662 +++++ pandas/core/groupby/groupby.py | 3495 +--------- pandas/core/groupby/grouper.py | 630 ++ pandas/core/groupby/ops.py | 908 +++ pandas/core/indexes/accessors.py | 85 +- pandas/core/indexes/api.py | 155 +- pandas/core/indexes/base.py | 780 ++- pandas/core/indexes/category.py | 146 +- pandas/core/indexes/datetimelike.py | 747 +-- pandas/core/indexes/datetimes.py | 1648 +---- pandas/core/indexes/frozen.py | 73 +- pandas/core/indexes/interval.py | 865 +-- pandas/core/indexes/multi.py | 302 +- pandas/core/indexes/numeric.py | 75 +- pandas/core/indexes/period.py | 1121 ++-- pandas/core/indexes/range.py | 119 +- pandas/core/indexes/timedeltas.py | 576 +- pandas/core/indexing.py | 537 +- pandas/core/internals.py | 5902 ----------------- pandas/core/internals/__init__.py | 14 + pandas/core/internals/blocks.py | 3300 +++++++++ pandas/core/internals/concat.py | 482 ++ pandas/core/internals/managers.py | 2065 ++++++ pandas/core/missing.py | 19 +- pandas/core/nanops.py | 450 +- pandas/core/ops.py | 706 +- pandas/core/panel.py | 96 +- pandas/core/resample.py | 173 +- pandas/core/reshape/api.py | 8 +- pandas/core/reshape/concat.py | 53 +- pandas/core/reshape/melt.py | 35 +- pandas/core/reshape/merge.py | 196 +- pandas/core/reshape/pivot.py | 86 +- pandas/core/reshape/reshape.py | 269 +- pandas/core/reshape/tile.py | 55 +- pandas/core/reshape/util.py | 12 +- pandas/core/series.py | 1000 +-- pandas/core/sorting.py | 59 +- pandas/core/sparse/api.py | 2 +- pandas/core/sparse/array.py | 861 --- pandas/core/sparse/frame.py | 161 +- pandas/core/sparse/series.py | 494 +- pandas/core/strings.py | 1162 ++-- pandas/core/tools/datetimes.py | 537 +- pandas/core/tools/numeric.py | 9 +- pandas/core/tools/timedeltas.py | 64 +- pandas/core/util/hashing.py | 18 +- pandas/core/window.py | 189 +- pandas/errors/__init__.py | 2 +- pandas/formats/style.py | 7 - pandas/io/api.py | 30 +- pandas/io/clipboard/clipboards.py | 4 +- pandas/io/clipboard/windows.py | 5 +- pandas/io/clipboards.py | 51 +- pandas/io/common.py | 87 +- pandas/io/date_converters.py | 3 +- pandas/io/excel.py | 153 +- pandas/io/feather_format.py | 55 +- pandas/io/formats/console.py | 82 +- pandas/io/formats/csvs.py | 113 +- pandas/io/formats/excel.py | 80 +- pandas/io/formats/format.py | 202 +- pandas/io/formats/html.py | 28 +- pandas/io/formats/latex.py | 13 +- pandas/io/formats/printing.py | 159 +- pandas/io/formats/style.py | 391 +- pandas/io/formats/templates/html.tpl | 22 +- pandas/io/formats/terminal.py | 19 +- pandas/io/gbq.py | 105 +- pandas/io/gcs.py | 16 + pandas/io/html.py | 461 +- pandas/io/json/json.py | 32 +- pandas/io/json/normalize.py | 29 +- pandas/io/json/table_schema.py | 15 +- pandas/io/msgpack/_packer.pyx | 16 +- pandas/io/msgpack/_unpacker.pyx | 26 +- pandas/io/packers.py | 76 +- pandas/io/parquet.py | 102 +- pandas/io/parsers.py | 230 +- pandas/io/pickle.py | 28 +- pandas/io/pytables.py | 451 +- pandas/io/s3.py | 3 +- pandas/io/sas/sas.pyx | 44 +- pandas/io/sas/sas7bdat.py | 79 +- pandas/io/sas/sas_xport.py | 22 +- pandas/io/sas/sasreader.py | 3 +- pandas/io/sql.py | 56 +- pandas/io/stata.py | 211 +- pandas/json.py | 7 - pandas/lib.py | 8 - pandas/parser.py | 8 - pandas/plotting/__init__.py | 8 +- pandas/plotting/_compat.py | 10 +- pandas/plotting/_converter.py | 34 +- pandas/plotting/_core.py | 237 +- pandas/plotting/_misc.py | 18 +- pandas/plotting/_style.py | 29 +- pandas/plotting/_timeseries.py | 23 +- pandas/plotting/_tools.py | 10 +- pandas/testing.py | 2 +- pandas/tests/api/test_api.py | 105 +- pandas/tests/api/test_types.py | 13 +- .../arithmetic}/__init__.py | 0 pandas/tests/arithmetic/conftest.py | 173 + pandas/tests/arithmetic/test_datetime64.py | 1961 ++++++ pandas/tests/arithmetic/test_numeric.py | 893 +++ pandas/tests/arithmetic/test_object.py | 212 + pandas/tests/arithmetic/test_period.py | 1069 +++ pandas/tests/arithmetic/test_timedelta64.py | 1406 ++++ pandas/{formats => tests/arrays}/__init__.py | 0 .../{ => arrays}/categorical/__init__.py | 0 .../tests/{ => arrays}/categorical/common.py | 0 .../{ => arrays}/categorical/conftest.py | 0 .../{ => arrays}/categorical/test_algos.py | 31 +- .../categorical/test_analytics.py | 13 +- .../{ => arrays}/categorical/test_api.py | 10 +- .../categorical/test_constructors.py | 96 +- .../{ => arrays}/categorical/test_dtypes.py | 9 +- .../{ => arrays}/categorical/test_indexing.py | 50 +- .../{ => arrays}/categorical/test_missing.py | 21 +- .../categorical/test_operators.py | 24 +- .../{ => arrays}/categorical/test_repr.py | 10 +- .../{ => arrays}/categorical/test_sorting.py | 2 +- .../{ => arrays}/categorical/test_subclass.py | 1 - .../{ => arrays}/categorical/test_warnings.py | 0 .../category => arrays/interval}/__init__.py | 0 pandas/tests/arrays/interval/test_interval.py | 72 + pandas/tests/arrays/interval/test_ops.py | 82 + .../arrays/sparse}/__init__.py | 0 .../{ => arrays}/sparse/test_arithmetics.py | 125 +- .../tests/{ => arrays}/sparse/test_array.py | 682 +- pandas/tests/arrays/sparse/test_dtype.py | 142 + .../{ => arrays}/sparse/test_libsparse.py | 36 +- pandas/tests/arrays/test_datetimelike.py | 230 + pandas/tests/arrays/test_integer.py | 698 ++ pandas/tests/arrays/test_period.py | 197 + pandas/tests/computation/test_eval.py | 23 +- pandas/tests/dtypes/test_cast.py | 262 +- pandas/tests/dtypes/test_common.py | 50 +- pandas/tests/dtypes/test_concat.py | 6 +- pandas/tests/dtypes/test_dtypes.py | 139 +- pandas/tests/dtypes/test_generic.py | 3 +- pandas/tests/dtypes/test_inference.py | 133 +- pandas/tests/dtypes/test_missing.py | 3 +- .../extension/arrow}/__init__.py | 0 pandas/tests/extension/arrow/bool.py | 136 + pandas/tests/extension/arrow/test_bool.py | 63 + pandas/tests/extension/base/__init__.py | 2 + pandas/tests/extension/base/base.py | 1 + pandas/tests/extension/base/constructors.py | 13 +- pandas/tests/extension/base/dtype.py | 43 + pandas/tests/extension/base/getitem.py | 17 +- pandas/tests/extension/base/groupby.py | 24 +- pandas/tests/extension/base/interface.py | 31 +- pandas/tests/extension/base/methods.py | 99 +- pandas/tests/extension/base/missing.py | 23 +- pandas/tests/extension/base/ops.py | 167 + pandas/tests/extension/base/reduce.py | 61 + pandas/tests/extension/base/reshaping.py | 75 +- pandas/tests/extension/base/setitem.py | 51 +- pandas/tests/extension/conftest.py | 27 + pandas/tests/extension/decimal/__init__.py | 4 + pandas/tests/extension/decimal/array.py | 71 +- .../tests/extension/decimal/test_decimal.py | 244 +- pandas/tests/extension/json/__init__.py | 3 + pandas/tests/extension/json/array.py | 34 +- pandas/tests/extension/json/test_json.py | 60 +- .../{category => }/test_categorical.py | 90 +- pandas/tests/extension/test_common.py | 25 +- pandas/tests/extension/test_external_block.py | 7 +- pandas/tests/extension/test_integer.py | 212 + pandas/tests/extension/test_interval.py | 149 + pandas/tests/extension/test_period.py | 157 + pandas/tests/extension/test_sparse.py | 318 + pandas/tests/frame/conftest.py | 223 + pandas/tests/frame/test_alter_axes.py | 939 +-- pandas/tests/frame/test_analytics.py | 1678 +++-- pandas/tests/frame/test_api.py | 206 +- pandas/tests/frame/test_apply.py | 635 +- pandas/tests/frame/test_arithmetic.py | 737 +- pandas/tests/frame/test_asof.py | 19 + .../tests/frame/test_axis_select_reindex.py | 61 +- pandas/tests/frame/test_block_internals.py | 189 +- pandas/tests/frame/test_combine_concat.py | 64 +- pandas/tests/frame/test_constructors.py | 135 +- pandas/tests/frame/test_convert_to.py | 9 +- pandas/tests/frame/test_dtypes.py | 244 +- pandas/tests/frame/test_duplicates.py | 457 ++ pandas/tests/frame/test_indexing.py | 533 +- pandas/tests/frame/test_missing.py | 26 +- pandas/tests/frame/test_mutate_columns.py | 2 +- pandas/tests/frame/test_operators.py | 729 +- pandas/tests/frame/test_period.py | 13 +- pandas/tests/frame/test_quantile.py | 10 +- pandas/tests/frame/test_query_eval.py | 22 +- pandas/tests/frame/test_rank.py | 136 +- pandas/tests/frame/test_replace.py | 98 +- pandas/tests/frame/test_reshape.py | 66 +- .../frame/test_sort_values_level_as_str.py | 33 +- pandas/tests/frame/test_sorting.py | 94 +- pandas/tests/frame/test_subclass.py | 44 +- pandas/tests/frame/test_timeseries.py | 32 +- pandas/tests/frame/test_timezones.py | 10 + pandas/tests/frame/test_to_csv.py | 224 +- pandas/tests/generic/test_generic.py | 35 +- .../generic/test_label_or_level_utils.py | 102 +- pandas/tests/generic/test_panel.py | 4 +- pandas/tests/generic/test_series.py | 19 + .../tests/groupby/aggregate/test_aggregate.py | 3 +- pandas/tests/groupby/aggregate/test_cython.py | 7 +- pandas/tests/groupby/aggregate/test_other.py | 12 +- pandas/tests/groupby/test_apply.py | 25 +- pandas/tests/groupby/test_bin_groupby.py | 8 +- pandas/tests/groupby/test_categorical.py | 43 +- pandas/tests/groupby/test_counting.py | 10 + pandas/tests/groupby/test_function.py | 108 +- pandas/tests/groupby/test_groupby.py | 120 +- pandas/tests/groupby/test_grouping.py | 56 +- pandas/tests/groupby/test_index_as_string.py | 48 - pandas/tests/groupby/test_nth.py | 61 +- pandas/tests/groupby/test_rank.py | 94 +- pandas/tests/groupby/test_timegrouper.py | 4 +- pandas/tests/groupby/test_transform.py | 98 +- pandas/tests/groupby/test_whitelist.py | 93 +- pandas/tests/indexes/common.py | 122 +- pandas/tests/indexes/conftest.py | 10 +- pandas/tests/indexes/data/mindex_073.pickle | Bin 670 -> 0 bytes .../tests/indexes/data/multiindex_v1.pickle | 149 - pandas/tests/indexes/datetimelike.py | 25 +- .../indexes/datetimes/test_arithmetic.py | 1016 +-- pandas/tests/indexes/datetimes/test_astype.py | 122 +- .../indexes/datetimes/test_construction.py | 114 +- .../indexes/datetimes/test_date_range.py | 458 +- .../tests/indexes/datetimes/test_datetime.py | 30 +- .../indexes/datetimes/test_datetimelike.py | 2 +- .../tests/indexes/datetimes/test_formats.py | 8 +- .../tests/indexes/datetimes/test_indexing.py | 34 +- pandas/tests/indexes/datetimes/test_misc.py | 25 +- pandas/tests/indexes/datetimes/test_ops.py | 96 +- .../indexes/datetimes/test_partial_slicing.py | 19 +- .../indexes/datetimes/test_scalar_compat.py | 81 +- pandas/tests/indexes/datetimes/test_setops.py | 13 +- .../tests/indexes/datetimes/test_timezones.py | 170 +- pandas/tests/indexes/datetimes/test_tools.py | 409 +- pandas/tests/indexes/interval/test_astype.py | 21 +- .../indexes/interval/test_construction.py | 90 +- .../tests/indexes/interval/test_interval.py | 296 +- .../indexes/interval/test_interval_new.py | 264 +- .../indexes/interval/test_interval_range.py | 36 +- .../indexes/interval/test_interval_tree.py | 45 +- pandas/tests/indexes/multi/__init__.py | 0 pandas/tests/indexes/multi/conftest.py | 56 + pandas/tests/indexes/multi/test_analytics.py | 325 + pandas/tests/indexes/multi/test_astype.py | 32 + pandas/tests/indexes/multi/test_compat.py | 124 + .../tests/indexes/multi/test_constructor.py | 477 ++ pandas/tests/indexes/multi/test_contains.py | 97 + pandas/tests/indexes/multi/test_conversion.py | 172 + pandas/tests/indexes/multi/test_copy.py | 87 + pandas/tests/indexes/multi/test_drop.py | 127 + pandas/tests/indexes/multi/test_duplicates.py | 265 + .../tests/indexes/multi/test_equivalence.py | 221 + pandas/tests/indexes/multi/test_format.py | 128 + pandas/tests/indexes/multi/test_get_set.py | 432 ++ pandas/tests/indexes/multi/test_indexing.py | 377 ++ pandas/tests/indexes/multi/test_integrity.py | 289 + pandas/tests/indexes/multi/test_join.py | 96 + pandas/tests/indexes/multi/test_missing.py | 128 + pandas/tests/indexes/multi/test_monotonic.py | 206 + pandas/tests/indexes/multi/test_names.py | 124 + .../indexes/multi/test_partial_indexing.py | 98 + pandas/tests/indexes/multi/test_reindex.py | 109 + pandas/tests/indexes/multi/test_reshape.py | 126 + pandas/tests/indexes/multi/test_set_ops.py | 223 + pandas/tests/indexes/multi/test_sorting.py | 265 + .../tests/indexes/period/test_arithmetic.py | 797 +-- pandas/tests/indexes/period/test_asfreq.py | 4 +- pandas/tests/indexes/period/test_astype.py | 6 +- .../tests/indexes/period/test_construction.py | 46 +- pandas/tests/indexes/period/test_formats.py | 59 +- pandas/tests/indexes/period/test_indexing.py | 48 +- pandas/tests/indexes/period/test_ops.py | 55 +- .../indexes/period/test_partial_slicing.py | 21 +- pandas/tests/indexes/period/test_period.py | 40 +- .../tests/indexes/period/test_period_range.py | 3 +- .../indexes/period/test_scalar_compat.py | 3 +- pandas/tests/indexes/period/test_setops.py | 7 +- pandas/tests/indexes/period/test_tools.py | 38 +- pandas/tests/indexes/test_base.py | 287 +- pandas/tests/indexes/test_category.py | 91 +- pandas/tests/indexes/test_frozen.py | 54 +- pandas/tests/indexes/test_multi.py | 3293 --------- pandas/tests/indexes/test_numeric.py | 322 +- pandas/tests/indexes/test_range.py | 81 +- .../indexes/timedeltas/test_arithmetic.py | 668 +- .../tests/indexes/timedeltas/test_astype.py | 9 +- .../indexes/timedeltas/test_construction.py | 11 +- .../tests/indexes/timedeltas/test_indexing.py | 15 +- pandas/tests/indexes/timedeltas/test_ops.py | 43 +- .../timedeltas/test_partial_slicing.py | 7 +- .../indexes/timedeltas/test_scalar_compat.py | 4 +- .../tests/indexes/timedeltas/test_setops.py | 2 +- .../indexes/timedeltas/test_timedelta.py | 20 +- .../timedeltas/test_timedelta_range.py | 9 +- pandas/tests/indexes/timedeltas/test_tools.py | 8 +- pandas/tests/indexing/common.py | 74 +- pandas/tests/indexing/conftest.py | 20 + .../tests/indexing/interval/test_interval.py | 11 +- .../indexing/interval/test_interval_new.py | 5 +- pandas/tests/indexing/test_callable.py | 1 + pandas/tests/indexing/test_categorical.py | 17 +- .../indexing/test_chaining_and_caching.py | 48 +- pandas/tests/indexing/test_coercion.py | 56 +- pandas/tests/indexing/test_datetime.py | 63 +- pandas/tests/indexing/test_floats.py | 26 +- pandas/tests/indexing/test_iloc.py | 54 +- pandas/tests/indexing/test_indexing.py | 129 +- .../tests/indexing/test_indexing_engines.py | 169 + pandas/tests/indexing/test_indexing_slow.py | 6 +- pandas/tests/indexing/test_ix.py | 29 +- pandas/tests/indexing/test_loc.py | 25 +- pandas/tests/indexing/test_multiindex.py | 209 +- pandas/tests/indexing/test_panel.py | 11 +- pandas/tests/indexing/test_partial.py | 22 +- pandas/tests/indexing/test_scalar.py | 38 +- pandas/tests/indexing/test_timedelta.py | 17 +- pandas/tests/internals/test_internals.py | 58 +- pandas/tests/io/conftest.py | 23 +- .../tests/io/formats/data/unicode_series.csv | 18 - pandas/tests/io/formats/test_console.py | 74 + pandas/tests/io/formats/test_format.py | 118 +- pandas/tests/io/formats/test_style.py | 366 +- pandas/tests/io/formats/test_to_csv.py | 356 +- pandas/tests/io/formats/test_to_excel.py | 7 +- pandas/tests/io/formats/test_to_html.py | 99 +- .../tests/io/generate_legacy_storage_files.py | 7 +- pandas/tests/io/json/test_compression.py | 40 +- .../tests/io/json/test_json_table_schema.py | 23 +- pandas/tests/io/json/test_normalize.py | 81 +- pandas/tests/io/json/test_pandas.py | 21 +- pandas/tests/io/json/test_ujson.py | 1946 ++---- pandas/tests/io/parser/c_parser_only.py | 31 +- pandas/tests/io/parser/comment.py | 5 +- pandas/tests/io/parser/common.py | 91 +- pandas/tests/io/parser/compression.py | 40 +- pandas/tests/io/parser/converters.py | 10 +- pandas/tests/io/parser/data/tar_csv.tar.gz | Bin 10240 -> 117 bytes pandas/tests/io/parser/dialect.py | 2 +- pandas/tests/io/parser/dtypes.py | 19 +- pandas/tests/io/parser/header.py | 56 +- pandas/tests/io/parser/index_col.py | 32 +- pandas/tests/io/parser/mangle_dupes.py | 21 +- pandas/tests/io/parser/multithread.py | 8 +- pandas/tests/io/parser/na_values.py | 40 +- pandas/tests/io/parser/parse_dates.py | 49 +- pandas/tests/io/parser/python_parser_only.py | 10 +- pandas/tests/io/parser/quoting.py | 22 +- pandas/tests/io/parser/skiprows.py | 6 +- pandas/tests/io/parser/test_network.py | 69 +- pandas/tests/io/parser/test_parsers.py | 48 +- pandas/tests/io/parser/test_read_fwf.py | 12 +- pandas/tests/io/parser/test_textreader.py | 25 +- pandas/tests/io/parser/test_unsupported.py | 31 +- pandas/tests/io/parser/usecols.py | 10 +- pandas/tests/io/sas/data/cars.sas7bdat | Bin 0 -> 13312 bytes pandas/tests/io/sas/data/load_log.sas7bdat | Bin 0 -> 589824 bytes pandas/tests/io/sas/data/many_columns.csv | 4 + .../tests/io/sas/data/many_columns.sas7bdat | Bin 0 -> 81920 bytes pandas/tests/io/sas/test_sas7bdat.py | 77 +- pandas/tests/io/sas/test_xport.py | 6 +- pandas/tests/io/test_clipboard.py | 234 +- pandas/tests/io/test_common.py | 139 +- pandas/tests/io/test_compression.py | 115 + pandas/tests/io/test_excel.py | 119 +- pandas/tests/io/test_feather.py | 51 +- pandas/tests/io/test_gbq.py | 75 +- pandas/tests/io/test_gcs.py | 62 + pandas/tests/io/test_html.py | 576 +- pandas/tests/io/test_packers.py | 86 +- pandas/tests/io/test_parquet.py | 81 +- pandas/tests/io/test_pickle.py | 52 +- pandas/tests/io/test_pytables.py | 179 +- pandas/tests/io/test_s3.py | 19 + pandas/tests/io/test_sql.py | 133 +- pandas/tests/io/test_stata.py | 108 +- pandas/tests/plotting/common.py | 55 +- pandas/tests/plotting/test_boxplot_method.py | 15 +- pandas/tests/plotting/test_converter.py | 3 +- pandas/tests/plotting/test_datetimelike.py | 272 +- pandas/tests/plotting/test_deprecated.py | 58 - pandas/tests/plotting/test_frame.py | 326 +- pandas/tests/plotting/test_hist_method.py | 19 +- pandas/tests/plotting/test_misc.py | 29 +- pandas/tests/plotting/test_series.py | 52 +- pandas/tests/reshape/merge/test_join.py | 21 +- pandas/tests/reshape/merge/test_merge.py | 81 +- pandas/tests/reshape/merge/test_merge_asof.py | 190 +- .../merge/test_merge_index_as_string.py | 36 - pandas/tests/reshape/test_concat.py | 173 +- pandas/tests/reshape/test_melt.py | 21 + pandas/tests/reshape/test_pivot.py | 211 +- pandas/tests/reshape/test_reshape.py | 179 +- pandas/tests/reshape/test_tile.py | 44 +- pandas/tests/scalar/interval/test_interval.py | 100 +- pandas/tests/scalar/interval/test_ops.py | 61 + pandas/tests/scalar/period/test_asfreq.py | 19 +- pandas/tests/scalar/period/test_period.py | 108 +- pandas/tests/scalar/test_nat.py | 20 +- .../tests/scalar/timedelta/test_arithmetic.py | 83 + .../tests/scalar/timedelta/test_timedelta.py | 147 +- .../tests/scalar/timestamp/test_arithmetic.py | 14 +- .../tests/scalar/timestamp/test_timestamp.py | 61 +- .../tests/scalar/timestamp/test_timezones.py | 73 +- .../tests/scalar/timestamp/test_unary_ops.py | 161 +- pandas/tests/series/common.py | 3 +- pandas/tests/series/conftest.py | 42 + .../tests/series/indexing/test_alter_index.py | 135 +- pandas/tests/series/indexing/test_boolean.py | 118 +- pandas/tests/series/indexing/test_datetime.py | 20 +- pandas/tests/series/indexing/test_iloc.py | 7 +- pandas/tests/series/indexing/test_indexing.py | 65 +- pandas/tests/series/indexing/test_loc.py | 11 +- pandas/tests/series/indexing/test_numeric.py | 10 +- pandas/tests/series/test_alter_axes.py | 160 +- pandas/tests/series/test_analytics.py | 993 +-- pandas/tests/series/test_api.py | 42 +- pandas/tests/series/test_apply.py | 202 +- pandas/tests/series/test_arithmetic.py | 946 +-- pandas/tests/series/test_asof.py | 10 +- pandas/tests/series/test_combine_concat.py | 96 +- pandas/tests/series/test_constructors.py | 224 +- pandas/tests/series/test_datetime_values.py | 215 +- pandas/tests/series/test_dtypes.py | 111 +- pandas/tests/series/test_duplicates.py | 139 + pandas/tests/series/test_internals.py | 25 +- pandas/tests/series/test_io.py | 141 +- pandas/tests/series/test_missing.py | 94 +- pandas/tests/series/test_operators.py | 1995 ++---- pandas/tests/series/test_period.py | 89 +- pandas/tests/series/test_quantile.py | 63 +- pandas/tests/series/test_rank.py | 42 +- pandas/tests/series/test_replace.py | 33 +- pandas/tests/series/test_repr.py | 44 +- pandas/tests/series/test_sorting.py | 16 +- pandas/tests/series/test_subclass.py | 25 +- pandas/tests/series/test_timeseries.py | 132 +- pandas/tests/series/test_timezones.py | 60 +- pandas/tests/series/test_validate.py | 12 +- pandas/tests/sparse/frame/conftest.py | 116 + pandas/tests/sparse/frame/test_analytics.py | 8 +- pandas/tests/sparse/frame/test_apply.py | 5 +- pandas/tests/sparse/frame/test_frame.py | 671 +- pandas/tests/sparse/frame/test_indexing.py | 16 +- .../tests/sparse/frame/test_to_from_scipy.py | 34 +- pandas/tests/sparse/series/test_indexing.py | 8 +- pandas/tests/sparse/series/test_series.py | 276 +- pandas/tests/sparse/test_combine_concat.py | 216 +- pandas/tests/sparse/test_format.py | 20 +- pandas/tests/sparse/test_groupby.py | 46 +- pandas/tests/sparse/test_indexing.py | 98 +- pandas/tests/sparse/test_pivot.py | 1 + pandas/tests/test_algos.py | 385 +- pandas/tests/test_base.py | 68 +- pandas/tests/test_common.py | 169 +- pandas/tests/test_config.py | 8 +- pandas/tests/test_downstream.py | 21 + pandas/tests/test_errors.py | 7 - pandas/tests/test_expressions.py | 19 +- pandas/tests/test_multilevel.py | 137 +- pandas/tests/test_nanops.py | 43 +- pandas/tests/test_panel.py | 3582 +++++----- pandas/tests/test_resample.py | 313 +- pandas/tests/test_sorting.py | 7 +- pandas/tests/test_strings.py | 373 +- pandas/tests/test_take.py | 525 +- pandas/tests/test_window.py | 180 +- pandas/tests/tools/test_numeric.py | 246 +- pandas/tests/tseries/conftest.py | 7 - pandas/tests/tseries/offsets/conftest.py | 15 +- pandas/tests/tseries/offsets/test_fiscal.py | 9 +- pandas/tests/tseries/offsets/test_offsets.py | 919 +-- .../offsets/test_offsets_properties.py | 110 + pandas/tests/tseries/offsets/test_ticks.py | 77 +- pandas/tests/tseries/test_frequencies.py | 4 +- pandas/tests/tslibs/test_api.py | 39 + pandas/tests/tslibs/test_array_to_datetime.py | 56 +- pandas/tests/tslibs/test_libfrequencies.py | 4 +- pandas/tests/tslibs/test_liboffsets.py | 4 +- pandas/tests/tslibs/test_parsing.py | 3 + pandas/tests/tslibs/test_timedeltas.py | 41 + pandas/tests/tslibs/test_timezones.py | 11 +- pandas/tests/tslibs/test_tslib.py | 23 + pandas/tests/util/test_hashing.py | 251 +- pandas/tests/util/test_testing.py | 69 +- pandas/tests/util/test_util.py | 44 +- pandas/tools/merge.py | 17 - pandas/tools/plotting.py | 20 - pandas/tseries/frequencies.py | 280 +- pandas/tseries/holiday.py | 15 +- pandas/tseries/offsets.py | 478 +- pandas/tslib.py | 7 - pandas/types/common.py | 8 - pandas/types/concat.py | 11 - pandas/util/_decorators.py | 104 +- pandas/util/_depr_module.py | 2 +- pandas/util/_doctools.py | 12 +- pandas/util/_exceptions.py | 16 + pandas/util/_print_versions.py | 17 +- pandas/util/_test_decorators.py | 35 +- pandas/util/_tester.py | 4 + pandas/util/_validators.py | 2 +- pandas/util/decorators.py | 8 - pandas/util/testing.py | 587 +- scripts/convert_deps.py | 2 + scripts/find_commits_touching_func.py | 10 +- scripts/find_undoc_args.py | 6 +- scripts/tests/__init__.py | 0 scripts/tests/conftest.py | 3 + scripts/tests/test_validate_docstrings.py | 953 +++ scripts/validate_docstrings.py | 850 ++- setup.cfg | 377 +- setup.py | 316 +- 833 files changed, 85941 insertions(+), 56869 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 .coveragerc delete mode 100644 appveyor.yml create mode 100644 asv_bench/benchmarks/indexing_engines.py delete mode 100644 asv_bench/vbench_to_asv.py create mode 100644 azure-pipelines.yml create mode 100644 ci/azure/linux.yml create mode 100644 ci/azure/macos.yml create mode 100644 ci/azure/windows-py27.yml create mode 100644 ci/azure/windows.yml delete mode 100644 ci/check_imports.py rename ci/{ => circle}/install_circle.sh (80%) create mode 100755 ci/circle/run_circle.sh rename ci/{ => circle}/show_circle.sh (100%) create mode 100755 ci/code_checks.sh rename ci/{circle-27-compat.yaml => deps/azure-27-compat.yaml} (65%) create mode 100644 ci/deps/azure-36-locale_slow.yaml rename ci/{circle-36-locale_slow.yaml => deps/azure-37-locale.yaml} (88%) rename ci/{travis-35-osx.yaml => deps/azure-macos-35.yaml} (74%) rename ci/{appveyor-27.yaml => deps/azure-windows-27.yaml} (75%) rename ci/{appveyor-36.yaml => deps/azure-windows-36.yaml} (73%) rename ci/{ => deps}/circle-36-locale.yaml (88%) rename ci/{ => deps}/travis-27-locale.yaml (77%) rename ci/{ => deps}/travis-27.yaml (81%) rename ci/{ => deps}/travis-36-doc.yaml (86%) rename ci/{ => deps}/travis-36-slow.yaml (89%) rename ci/{ => deps}/travis-36.yaml (74%) rename ci/{travis-36-numpydev.yaml => deps/travis-37-numpydev.yaml} (83%) rename ci/{circle-35-ascii.yaml => deps/travis-37.yaml} (56%) create mode 100644 ci/incremental/build.cmd create mode 100755 ci/incremental/build.sh create mode 100755 ci/incremental/install_miniconda.sh create mode 100644 ci/incremental/setup_conda_environment.cmd create mode 100755 ci/incremental/setup_conda_environment.sh delete mode 100644 ci/install.ps1 delete mode 100755 ci/install_db_circle.sh delete mode 100755 ci/lint.sh delete mode 100755 ci/run_circle.sh delete mode 100644 circle.yml create mode 100644 doc/cheatsheet/Pandas_Cheat_Sheet_JA.pdf create mode 100644 doc/cheatsheet/Pandas_Cheat_Sheet_JA.pptx create mode 100644 doc/source/names_wordlist.txt create mode 100644 doc/source/spelling_wordlist.txt create mode 100644 doc/source/whatsnew/v0.23.2.txt create mode 100644 doc/source/whatsnew/v0.23.3.txt create mode 100644 doc/source/whatsnew/v0.23.4.txt create mode 100644 doc/source/whatsnew/v0.23.5.txt delete mode 100644 pandas/_libs/join_func_helper.pxi.in delete mode 100644 pandas/_libs/join_helper.pxi.in delete mode 100644 pandas/_libs/reshape_helper.pxi.in delete mode 100644 pandas/_libs/src/datetime/np_datetime.c delete mode 100644 pandas/_libs/src/datetime/np_datetime.h delete mode 100644 pandas/_libs/src/datetime/np_datetime_strings.c delete mode 100644 pandas/_libs/src/inference.pyx rename pandas/_libs/src/{helper.h => inline_helper.h} (80%) delete mode 100644 pandas/_libs/src/numpy_helper.h delete mode 100644 pandas/_libs/src/period_helper.c delete mode 100644 pandas/_libs/src/period_helper.h delete mode 100644 pandas/_libs/src/util.pxd create mode 100644 pandas/_libs/tslibs/offsets.pxd create mode 100644 pandas/_libs/tslibs/src/datetime/np_datetime.h rename pandas/_libs/{ => tslibs}/src/datetime/np_datetime_strings.h (76%) create mode 100644 pandas/_libs/tslibs/util.pxd create mode 100644 pandas/_libs/util.pxd delete mode 100644 pandas/computation/expressions.py create mode 100644 pandas/core/arrays/datetimelike.py create mode 100644 pandas/core/arrays/datetimes.py create mode 100644 pandas/core/arrays/integer.py create mode 100644 pandas/core/arrays/interval.py create mode 100644 pandas/core/arrays/period.py create mode 100644 pandas/core/arrays/sparse.py create mode 100644 pandas/core/arrays/timedeltas.py delete mode 100644 pandas/core/datetools.py create mode 100644 pandas/core/groupby/base.py create mode 100644 pandas/core/groupby/categorical.py create mode 100644 pandas/core/groupby/generic.py create mode 100644 pandas/core/groupby/grouper.py create mode 100644 pandas/core/groupby/ops.py delete mode 100644 pandas/core/internals.py create mode 100644 pandas/core/internals/__init__.py create mode 100644 pandas/core/internals/blocks.py create mode 100644 pandas/core/internals/concat.py create mode 100644 pandas/core/internals/managers.py delete mode 100644 pandas/core/sparse/array.py delete mode 100644 pandas/formats/style.py create mode 100644 pandas/io/gcs.py delete mode 100644 pandas/json.py delete mode 100644 pandas/lib.py delete mode 100644 pandas/parser.py rename pandas/{computation => tests/arithmetic}/__init__.py (100%) create mode 100644 pandas/tests/arithmetic/conftest.py create mode 100644 pandas/tests/arithmetic/test_datetime64.py create mode 100644 pandas/tests/arithmetic/test_numeric.py create mode 100644 pandas/tests/arithmetic/test_object.py create mode 100644 pandas/tests/arithmetic/test_period.py create mode 100644 pandas/tests/arithmetic/test_timedelta64.py rename pandas/{formats => tests/arrays}/__init__.py (100%) rename pandas/tests/{ => arrays}/categorical/__init__.py (100%) rename pandas/tests/{ => arrays}/categorical/common.py (100%) rename pandas/tests/{ => arrays}/categorical/conftest.py (100%) rename pandas/tests/{ => arrays}/categorical/test_algos.py (74%) rename pandas/tests/{ => arrays}/categorical/test_analytics.py (98%) rename pandas/tests/{ => arrays}/categorical/test_api.py (99%) rename pandas/tests/{ => arrays}/categorical/test_constructors.py (89%) rename pandas/tests/{ => arrays}/categorical/test_dtypes.py (99%) rename pandas/tests/{ => arrays}/categorical/test_indexing.py (66%) rename pandas/tests/{ => arrays}/categorical/test_missing.py (81%) rename pandas/tests/{ => arrays}/categorical/test_operators.py (95%) rename pandas/tests/{ => arrays}/categorical/test_repr.py (99%) rename pandas/tests/{ => arrays}/categorical/test_sorting.py (100%) rename pandas/tests/{ => arrays}/categorical/test_subclass.py (99%) rename pandas/tests/{ => arrays}/categorical/test_warnings.py (100%) rename pandas/tests/{extension/category => arrays/interval}/__init__.py (100%) create mode 100644 pandas/tests/arrays/interval/test_interval.py create mode 100644 pandas/tests/arrays/interval/test_ops.py rename pandas/{tools => tests/arrays/sparse}/__init__.py (100%) rename pandas/tests/{ => arrays}/sparse/test_arithmetics.py (83%) rename pandas/tests/{ => arrays}/sparse/test_array.py (59%) create mode 100644 pandas/tests/arrays/sparse/test_dtype.py rename pandas/tests/{ => arrays}/sparse/test_libsparse.py (97%) create mode 100644 pandas/tests/arrays/test_datetimelike.py create mode 100644 pandas/tests/arrays/test_integer.py create mode 100644 pandas/tests/arrays/test_period.py rename pandas/{types => tests/extension/arrow}/__init__.py (100%) create mode 100644 pandas/tests/extension/arrow/bool.py create mode 100644 pandas/tests/extension/arrow/test_bool.py create mode 100644 pandas/tests/extension/base/ops.py create mode 100644 pandas/tests/extension/base/reduce.py rename pandas/tests/extension/{category => }/test_categorical.py (53%) create mode 100644 pandas/tests/extension/test_integer.py create mode 100644 pandas/tests/extension/test_interval.py create mode 100644 pandas/tests/extension/test_period.py create mode 100644 pandas/tests/extension/test_sparse.py create mode 100644 pandas/tests/frame/conftest.py create mode 100644 pandas/tests/frame/test_duplicates.py delete mode 100644 pandas/tests/indexes/data/mindex_073.pickle delete mode 100644 pandas/tests/indexes/data/multiindex_v1.pickle create mode 100644 pandas/tests/indexes/multi/__init__.py create mode 100644 pandas/tests/indexes/multi/conftest.py create mode 100644 pandas/tests/indexes/multi/test_analytics.py create mode 100644 pandas/tests/indexes/multi/test_astype.py create mode 100644 pandas/tests/indexes/multi/test_compat.py create mode 100644 pandas/tests/indexes/multi/test_constructor.py create mode 100644 pandas/tests/indexes/multi/test_contains.py create mode 100644 pandas/tests/indexes/multi/test_conversion.py create mode 100644 pandas/tests/indexes/multi/test_copy.py create mode 100644 pandas/tests/indexes/multi/test_drop.py create mode 100644 pandas/tests/indexes/multi/test_duplicates.py create mode 100644 pandas/tests/indexes/multi/test_equivalence.py create mode 100644 pandas/tests/indexes/multi/test_format.py create mode 100644 pandas/tests/indexes/multi/test_get_set.py create mode 100644 pandas/tests/indexes/multi/test_indexing.py create mode 100644 pandas/tests/indexes/multi/test_integrity.py create mode 100644 pandas/tests/indexes/multi/test_join.py create mode 100644 pandas/tests/indexes/multi/test_missing.py create mode 100644 pandas/tests/indexes/multi/test_monotonic.py create mode 100644 pandas/tests/indexes/multi/test_names.py create mode 100644 pandas/tests/indexes/multi/test_partial_indexing.py create mode 100644 pandas/tests/indexes/multi/test_reindex.py create mode 100644 pandas/tests/indexes/multi/test_reshape.py create mode 100644 pandas/tests/indexes/multi/test_set_ops.py create mode 100644 pandas/tests/indexes/multi/test_sorting.py delete mode 100644 pandas/tests/indexes/test_multi.py create mode 100644 pandas/tests/indexing/conftest.py create mode 100644 pandas/tests/indexing/test_indexing_engines.py delete mode 100644 pandas/tests/io/formats/data/unicode_series.csv create mode 100644 pandas/tests/io/formats/test_console.py create mode 100644 pandas/tests/io/sas/data/cars.sas7bdat create mode 100644 pandas/tests/io/sas/data/load_log.sas7bdat create mode 100644 pandas/tests/io/sas/data/many_columns.csv create mode 100644 pandas/tests/io/sas/data/many_columns.sas7bdat create mode 100644 pandas/tests/io/test_compression.py create mode 100644 pandas/tests/io/test_gcs.py delete mode 100644 pandas/tests/plotting/test_deprecated.py create mode 100644 pandas/tests/scalar/interval/test_ops.py create mode 100644 pandas/tests/series/conftest.py create mode 100644 pandas/tests/series/test_duplicates.py create mode 100644 pandas/tests/sparse/frame/conftest.py delete mode 100644 pandas/tests/tseries/conftest.py create mode 100644 pandas/tests/tseries/offsets/test_offsets_properties.py create mode 100644 pandas/tests/tslibs/test_api.py create mode 100644 pandas/tests/tslibs/test_timedeltas.py create mode 100644 pandas/tests/tslibs/test_tslib.py delete mode 100644 pandas/tools/merge.py delete mode 100644 pandas/tools/plotting.py delete mode 100644 pandas/tslib.py delete mode 100644 pandas/types/common.py delete mode 100644 pandas/types/concat.py create mode 100644 pandas/util/_exceptions.py delete mode 100644 pandas/util/decorators.py create mode 100644 scripts/tests/__init__.py create mode 100644 scripts/tests/conftest.py create mode 100644 scripts/tests/test_validate_docstrings.py diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..cdfe93613fbdd --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,42 @@ +version: 2 +jobs: + + # -------------------------------------------------------------------------- + # 1. py36_locale + # -------------------------------------------------------------------------- + py36_locale: + docker: + - image: continuumio/miniconda:latest + # databases configuration + - image: circleci/postgres:9.6.5-alpine-ram + environment: + POSTGRES_USER: postgres + POSTGRES_DB: pandas_nosetest + - image: circleci/mysql:8-ram + environment: + MYSQL_USER: "root" + MYSQL_HOST: "localhost" + MYSQL_ALLOW_EMPTY_PASSWORD: "true" + MYSQL_DATABASE: "pandas_nosetest" + + environment: + JOB: "3.6_LOCALE" + ENV_FILE: "ci/deps/circle-36-locale.yaml" + LOCALE_OVERRIDE: "zh_CN.UTF-8" + MINICONDA_DIR: /home/ubuntu/miniconda3 + steps: + - checkout + - run: + name: build + command: | + ./ci/circle/install_circle.sh + ./ci/circle/show_circle.sh + - run: + name: test + command: ./ci/circle/run_circle.sh --skip-slow --skip-network + +workflows: + version: 2 + build_and_test: + jobs: + - py36_locale diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 3f630aa6cf8f5..0000000000000 --- a/.coveragerc +++ /dev/null @@ -1,27 +0,0 @@ -# .coveragerc to control coverage.py -[run] -branch = False -omit = */tests/* - -[report] -# Regexes for lines to exclude from consideration -exclude_lines = - # Have to re-enable the standard pragma - pragma: no cover - - # Don't complain about missing debug-only code: - def __repr__ - if self\.debug - - # Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - - # Don't complain if non-runnable code isn't run: - if 0: - if __name__ == .__main__.: - -ignore_errors = False - -[html] -directory = coverage_html_report diff --git a/.gitignore b/.gitignore index 96b1f945870de..a59f2843c365a 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,8 @@ dist coverage.xml coverage_html_report *.pytest_cache +# hypothesis test database +.hypothesis/ # OS generated files # ###################### diff --git a/.pep8speaks.yml b/.pep8speaks.yml index fda26d87bf7f6..ae1c92832bb15 100644 --- a/.pep8speaks.yml +++ b/.pep8speaks.yml @@ -3,10 +3,18 @@ scanner: diff_only: True # If True, errors caused by only the patch are shown +# Opened issue in pep8speaks, so we can directly use the config in setup.cfg +# (and avoid having to duplicate it here): +# https://github.com/OrkoHunter/pep8speaks/issues/95 + pycodestyle: max-line-length: 79 - ignore: # Errors and warnings to ignore + ignore: + - W503, # line break before binary operator + - W504, # line break after binary operator - E402, # module level import not at top of file + - E722, # do not use bare except - E731, # do not assign a lambda expression, use a def - - E741, # do not use variables named 'l', 'O', or 'I' - - W503 # line break before binary operator + - C406, # Unnecessary list literal - rewrite as a dict literal. + - C408, # Unnecessary dict call - rewrite as a literal. + - C409 # Unnecessary list passed to tuple() - rewrite as a tuple literal. diff --git a/.travis.yml b/.travis.yml index 4e25380a7d941..9fac09e1fa788 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,58 +30,53 @@ matrix: exclude: # Exclude the default Python 3.5 build - python: 3.5 + include: - - os: osx - language: generic + - dist: trusty env: - - JOB="3.5, OSX" ENV_FILE="ci/travis-35-osx.yaml" TEST_ARGS="--skip-slow --skip-network" + - JOB="3.7" ENV_FILE="ci/deps/travis-37.yaml" TEST_ARGS="--skip-slow --skip-network" + - dist: trusty env: - - JOB="2.7, locale, slow, old NumPy" ENV_FILE="ci/travis-27-locale.yaml" LOCALE_OVERRIDE="zh_CN.UTF-8" SLOW=true + - JOB="2.7, locale, slow, old NumPy" ENV_FILE="ci/deps/travis-27-locale.yaml" LOCALE_OVERRIDE="zh_CN.UTF-8" SLOW=true addons: apt: packages: - language-pack-zh-hans - dist: trusty env: - - JOB="2.7, lint" ENV_FILE="ci/travis-27.yaml" TEST_ARGS="--skip-slow" LINT=true + - JOB="2.7" ENV_FILE="ci/deps/travis-27.yaml" TEST_ARGS="--skip-slow" addons: apt: packages: - python-gtk2 - dist: trusty env: - - JOB="3.6, coverage" ENV_FILE="ci/travis-36.yaml" TEST_ARGS="--skip-slow --skip-network" PANDAS_TESTING_MODE="deprecate" COVERAGE=true - # In allow_failures - - dist: trusty - env: - - JOB="3.6, slow" ENV_FILE="ci/travis-36-slow.yaml" SLOW=true - # In allow_failures + - JOB="3.6, lint, coverage" ENV_FILE="ci/deps/travis-36.yaml" TEST_ARGS="--skip-slow --skip-network" PANDAS_TESTING_MODE="deprecate" COVERAGE=true LINT=true - dist: trusty env: - - JOB="3.6, NumPy dev" ENV_FILE="ci/travis-36-numpydev.yaml" TEST_ARGS="--skip-slow --skip-network" PANDAS_TESTING_MODE="deprecate" + - JOB="3.7, NumPy dev" ENV_FILE="ci/deps/travis-37-numpydev.yaml" TEST_ARGS="--skip-slow --skip-network -W error" PANDAS_TESTING_MODE="deprecate" addons: apt: packages: - xsel + # In allow_failures - dist: trusty env: - - JOB="3.6, doc" ENV_FILE="ci/travis-36-doc.yaml" DOC=true + - JOB="3.6, slow" ENV_FILE="ci/deps/travis-36-slow.yaml" SLOW=true + + # In allow_failures + - dist: trusty + env: + - JOB="3.6, doc" ENV_FILE="ci/deps/travis-36-doc.yaml" DOC=true allow_failures: - dist: trusty env: - - JOB="3.6, slow" ENV_FILE="ci/travis-36-slow.yaml" SLOW=true - - dist: trusty - env: - - JOB="3.6, NumPy dev" ENV_FILE="ci/travis-36-numpydev.yaml" TEST_ARGS="--skip-slow --skip-network" PANDAS_TESTING_MODE="deprecate" - addons: - apt: - packages: - - xsel + - JOB="3.6, slow" ENV_FILE="ci/deps/travis-36-slow.yaml" SLOW=true - dist: trusty env: - - JOB="3.6, doc" ENV_FILE="ci/travis-36-doc.yaml" DOC=true + - JOB="3.6, doc" ENV_FILE="ci/deps/travis-36-doc.yaml" DOC=true before_install: - echo "before_install" @@ -113,10 +108,7 @@ script: - ci/run_build_docs.sh - ci/script_single.sh - ci/script_multi.sh - - ci/lint.sh - - echo "checking imports" - - source activate pandas && python ci/check_imports.py - - echo "script done" + - ci/code_checks.sh after_success: - ci/upload_coverage.sh @@ -124,10 +116,10 @@ after_success: after_script: - echo "after_script start" - source activate pandas && pushd /tmp && python -c "import pandas; pandas.show_versions();" && popd - - if [ -e /tmp/single.xml ]; then - ci/print_skipped.py /tmp/single.xml; + - if [ -e test-data-single.xml ]; then + ci/print_skipped.py test-data-single.xml; fi - - if [ -e /tmp/multiple.xml ]; then - ci/print_skipped.py /tmp/multiple.xml; + - if [ -e test-data-multiple.xml ]; then + ci/print_skipped.py test-data-multiple.xml; fi - echo "after_script done" diff --git a/MANIFEST.in b/MANIFEST.in index 9773019c6e6e0..b417b8890fa24 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,27 +3,39 @@ include LICENSE include RELEASE.md include README.md include setup.py -include pyproject.toml graft doc prune doc/build +graft LICENSES + graft pandas -global-exclude *.so -global-exclude *.pyd +global-exclude *.bz2 +global-exclude *.csv +global-exclude *.dta +global-exclude *.gz +global-exclude *.h5 +global-exclude *.html +global-exclude *.json +global-exclude *.msgpack +global-exclude *.pickle +global-exclude *.png global-exclude *.pyc +global-exclude *.pyd +global-exclude *.sas7bdat +global-exclude *.so +global-exclude *.xls +global-exclude *.xlsm +global-exclude *.xlsx +global-exclude *.xpt +global-exclude *.xz +global-exclude *.zip global-exclude *~ -global-exclude \#* -global-exclude .git* global-exclude .DS_Store -global-exclude *.png +global-exclude .git* +global-exclude \#* -# include examples/data/* -# recursive-include examples *.py -# recursive-include doc/source * -# recursive-include doc/sphinxext * -# recursive-include LICENSES * include versioneer.py include pandas/_version.py include pandas/io/formats/templates/*.tpl diff --git a/Makefile b/Makefile index c79175cd3c401..4a4aca21e1b78 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ build: clean_pyc python setup.py build_ext --inplace lint-diff: - git diff master --name-only -- "*.py" | grep "pandas" | xargs flake8 + git diff master --name-only -- "*.py" | grep -E "pandas|scripts" | xargs flake8 develop: build -python setup.py develop @@ -23,3 +23,4 @@ doc: cd doc; \ python make.py clean; \ python make.py html + python make.py spellcheck diff --git a/README.md b/README.md index 3c8fe57400099..b4dedecb4c697 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@ - - appveyor build status + + Azure Pipelines build status @@ -89,7 +89,7 @@ -## What is it +## What is it? **pandas** is a Python package providing fast, flexible, and expressive data structures designed to make working with "relational" or "labeled" data both @@ -97,7 +97,7 @@ easy and intuitive. It aims to be the fundamental high-level building block for doing practical, **real world** data analysis in Python. Additionally, it has the broader goal of becoming **the most powerful and flexible open source data analysis / manipulation tool available in any language**. It is already well on -its way toward this goal. +its way towards this goal. ## Main Features Here are just a few of the things that pandas does well: diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index f70fc829ec971..0000000000000 --- a/appveyor.yml +++ /dev/null @@ -1,89 +0,0 @@ -# With infos from -# http://tjelvarolsson.com/blog/how-to-continuously-test-your-python-code-on-windows-using-appveyor/ -# https://packaging.python.org/en/latest/appveyor/ -# https://github.com/rmcgibbo/python-appveyor-conda-example - -# Backslashes in quotes need to be escaped: \ -> "\\" - -matrix: - fast_finish: true # immediately finish build once one of the jobs fails. - -environment: - global: - # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the - # /E:ON and /V:ON options are not enabled in the batch script interpreter - # See: http://stackoverflow.com/a/13751649/163740 - CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci\\run_with_env.cmd" - clone_folder: C:\projects\pandas - PANDAS_TESTING_MODE: "deprecate" - - matrix: - - - CONDA_ROOT: "C:\\Miniconda3_64" - PYTHON_VERSION: "3.6" - PYTHON_ARCH: "64" - CONDA_PY: "36" - CONDA_NPY: "113" - - - CONDA_ROOT: "C:\\Miniconda3_64" - PYTHON_VERSION: "2.7" - PYTHON_ARCH: "64" - CONDA_PY: "27" - CONDA_NPY: "110" - -# We always use a 64-bit machine, but can build x86 distributions -# with the PYTHON_ARCH variable (which is used by CMD_IN_ENV). -platform: - - x64 - -# all our python builds have to happen in tests_script... -build: false - -install: - # cancel older builds for the same PR - - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` - https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` - Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` - throw "There are newer queued builds for this pull request, failing early." } - - # this installs the appropriate Miniconda (Py2/Py3, 32/64 bit) - # updates conda & installs: conda-build jinja2 anaconda-client - - powershell .\ci\install.ps1 - - SET PATH=%CONDA_ROOT%;%CONDA_ROOT%\Scripts;%PATH% - - echo "install" - - cd - - ls -ltr - - git tag --sort v:refname - - # this can conflict with git - - cmd: rmdir C:\cygwin /s /q - - # install our build environment - - cmd: conda config --set show_channel_urls true --set always_yes true --set changeps1 false - - cmd: conda update -q conda - - cmd: conda config --set ssl_verify false - - # add the pandas channel *before* defaults to have defaults take priority - - cmd: conda config --add channels conda-forge - - cmd: conda config --add channels pandas - - cmd: conda config --remove channels defaults - - cmd: conda config --add channels defaults - - # this is now the downloaded conda... - - cmd: conda info -a - - # create our env - - cmd: conda env create -q -n pandas --file=ci\appveyor-%CONDA_PY%.yaml - - cmd: activate pandas - - cmd: conda list -n pandas - # uninstall pandas if it's present - - cmd: conda remove pandas -y --force & exit 0 - - cmd: pip uninstall -y pandas & exit 0 - - # build em using the local source checkout in the correct windows env - - cmd: '%CMD_IN_ENV% python setup.py build_ext --inplace' - -test_script: - # tests - - cmd: activate pandas - - cmd: test.bat diff --git a/asv_bench/benchmarks/algorithms.py b/asv_bench/benchmarks/algorithms.py index cccd38ef11251..1ab88dc9f9e6d 100644 --- a/asv_bench/benchmarks/algorithms.py +++ b/asv_bench/benchmarks/algorithms.py @@ -9,16 +9,12 @@ try: hashing = import_module(imp) break - except: + except (ImportError, TypeError, ValueError): pass -from .pandas_vb_common import setup # noqa - class Factorize(object): - goal_time = 0.2 - params = [True, False] param_names = ['sort'] @@ -40,8 +36,6 @@ def time_factorize_string(self, sort): class Duplicated(object): - goal_time = 0.2 - params = ['first', 'last', False] param_names = ['keep'] @@ -63,8 +57,6 @@ def time_duplicated_string(self, keep): class DuplicatedUniqueIndex(object): - goal_time = 0.2 - def setup(self): N = 10**5 self.idx_int_dup = pd.Int64Index(np.arange(N * 5)) @@ -77,8 +69,6 @@ def time_duplicated_unique_int(self): class Match(object): - goal_time = 0.2 - def setup(self): self.uniques = tm.makeStringIndex(1000).values self.all = self.uniques.repeat(10) @@ -90,8 +80,6 @@ def time_match_string(self): class Hashing(object): - goal_time = 0.2 - def setup_cache(self): N = 10**5 @@ -126,3 +114,6 @@ def time_series_timedeltas(self, df): def time_series_dates(self, df): hashing.hash_pandas_object(df['dates']) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/attrs_caching.py b/asv_bench/benchmarks/attrs_caching.py index 48f0b7d71144c..d061755208c9e 100644 --- a/asv_bench/benchmarks/attrs_caching.py +++ b/asv_bench/benchmarks/attrs_caching.py @@ -5,13 +5,9 @@ except ImportError: from pandas.util.decorators import cache_readonly -from .pandas_vb_common import setup # noqa - class DataFrameAttributes(object): - goal_time = 0.2 - def setup(self): self.df = DataFrame(np.random.randn(10, 6)) self.cur_index = self.df.index @@ -25,8 +21,6 @@ def time_set_index(self): class CacheReadonly(object): - goal_time = 0.2 - def setup(self): class Foo: @@ -38,3 +32,6 @@ def prop(self): def time_cache_readonly(self): self.obj.prop + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/binary_ops.py b/asv_bench/benchmarks/binary_ops.py index cc8766e1fa39c..dfdebec86d67c 100644 --- a/asv_bench/benchmarks/binary_ops.py +++ b/asv_bench/benchmarks/binary_ops.py @@ -6,13 +6,9 @@ except ImportError: import pandas.computation.expressions as expr -from .pandas_vb_common import setup # noqa - class Ops(object): - goal_time = 0.2 - params = [[True, False], ['default', 1]] param_names = ['use_numexpr', 'threads'] @@ -44,8 +40,6 @@ def teardown(self, use_numexpr, threads): class Ops2(object): - goal_time = 0.2 - def setup(self): N = 10**3 self.df = DataFrame(np.random.randn(N, N)) @@ -83,8 +77,6 @@ def time_frame_float_mod(self): class Timeseries(object): - goal_time = 0.2 - params = [None, 'US/Eastern'] param_names = ['tz'] @@ -111,8 +103,6 @@ def time_timestamp_ops_diff_with_shift(self, tz): class AddOverflowScalar(object): - goal_time = 0.2 - params = [1, -1, 0] param_names = ['scalar'] @@ -126,8 +116,6 @@ def time_add_overflow_scalar(self, scalar): class AddOverflowArray(object): - goal_time = 0.2 - def setup(self): N = 10**6 self.arr = np.arange(N) @@ -149,3 +137,6 @@ def time_add_overflow_b_mask_nan(self): def time_add_overflow_both_arg_nan(self): checked_add_with_arr(self.arr, self.arr_mixed, arr_mask=self.arr_nan_1, b_mask=self.arr_nan_2) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/categoricals.py b/asv_bench/benchmarks/categoricals.py index 5464e7cba22c3..8a0fbc48755b5 100644 --- a/asv_bench/benchmarks/categoricals.py +++ b/asv_bench/benchmarks/categoricals.py @@ -11,13 +11,9 @@ except ImportError: pass -from .pandas_vb_common import setup # noqa - class Concat(object): - goal_time = 0.2 - def setup(self): N = 10**5 self.s = pd.Series(list('aabbcd') * N).astype('category') @@ -34,8 +30,6 @@ def time_union(self): class Constructor(object): - goal_time = 0.2 - def setup(self): N = 10**5 self.categories = list('abcde') @@ -77,8 +71,6 @@ def time_from_codes_all_int8(self): class ValueCounts(object): - goal_time = 0.2 - params = [True, False] param_names = ['dropna'] @@ -93,8 +85,6 @@ def time_value_counts(self, dropna): class Repr(object): - goal_time = 0.2 - def setup(self): self.sel = pd.Series(['s1234']).astype('category') @@ -104,8 +94,6 @@ def time_rendering(self): class SetCategories(object): - goal_time = 0.2 - def setup(self): n = 5 * 10**5 arr = ['s%04d' % i for i in np.random.randint(0, n // 10, size=n)] @@ -117,8 +105,6 @@ def time_set_categories(self): class Rank(object): - goal_time = 0.2 - def setup(self): N = 10**5 ncats = 100 @@ -156,8 +142,6 @@ def time_rank_int_cat_ordered(self): class Isin(object): - goal_time = 0.2 - params = ['object', 'int64'] param_names = ['dtype'] @@ -193,3 +177,55 @@ def time_categorical_series_is_monotonic_increasing(self): def time_categorical_series_is_monotonic_decreasing(self): self.s.is_monotonic_decreasing + + +class Contains(object): + + def setup(self): + N = 10**5 + self.ci = tm.makeCategoricalIndex(N) + self.c = self.ci.values + self.key = self.ci.categories[0] + + def time_categorical_index_contains(self): + self.key in self.ci + + def time_categorical_contains(self): + self.key in self.c + + +class CategoricalSlicing(object): + + params = ['monotonic_incr', 'monotonic_decr', 'non_monotonic'] + param_names = ['index'] + + def setup(self, index): + N = 10**6 + values = list('a' * N + 'b' * N + 'c' * N) + indices = { + 'monotonic_incr': pd.Categorical(values), + 'monotonic_decr': pd.Categorical(reversed(values)), + 'non_monotonic': pd.Categorical(list('abc' * N))} + self.data = indices[index] + + self.scalar = 10000 + self.list = list(range(10000)) + self.cat_scalar = 'b' + + def time_getitem_scalar(self, index): + self.data[self.scalar] + + def time_getitem_slice(self, index): + self.data[:self.scalar] + + def time_getitem_list_like(self, index): + self.data[[self.scalar]] + + def time_getitem_list(self, index): + self.data[self.list] + + def time_getitem_bool_array(self, index): + self.data[self.data == self.cat_scalar] + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/ctors.py b/asv_bench/benchmarks/ctors.py index 3f9016787aab4..198ed1c90a2e9 100644 --- a/asv_bench/benchmarks/ctors.py +++ b/asv_bench/benchmarks/ctors.py @@ -2,13 +2,9 @@ import pandas.util.testing as tm from pandas import Series, Index, DatetimeIndex, Timestamp, MultiIndex -from .pandas_vb_common import setup # noqa - class SeriesConstructors(object): - goal_time = 0.2 - param_names = ["data_fmt", "with_index"] params = [[lambda x: x, list, @@ -32,8 +28,6 @@ def time_series_constructor(self, data_fmt, with_index): class SeriesDtypesConstructors(object): - goal_time = 0.2 - def setup(self): N = 10**4 self.arr = np.random.randn(N, N) @@ -56,11 +50,12 @@ def time_dtindex_from_index_with_series(self): class MultiIndexConstructor(object): - goal_time = 0.2 - def setup(self): N = 10**4 self.iterables = [tm.makeStringIndex(N), range(20)] def time_multiindex_from_iterables(self): MultiIndex.from_product(self.iterables) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/eval.py b/asv_bench/benchmarks/eval.py index 8e581dcf22b4c..837478efbad64 100644 --- a/asv_bench/benchmarks/eval.py +++ b/asv_bench/benchmarks/eval.py @@ -5,13 +5,9 @@ except ImportError: import pandas.computation.expressions as expr -from .pandas_vb_common import setup # noqa - class Eval(object): - goal_time = 0.2 - params = [['numexpr', 'python'], [1, 'all']] param_names = ['engine', 'threads'] @@ -43,8 +39,6 @@ def teardown(self, engine, threads): class Query(object): - goal_time = 0.2 - def setup(self): N = 10**6 halfway = (N // 2) - 1 @@ -65,3 +59,6 @@ def time_query_datetime_column(self): def time_query_with_boolean_selection(self): self.df.query('(a >= @self.min_val) & (a <= @self.max_val)') + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/frame_ctor.py b/asv_bench/benchmarks/frame_ctor.py index 9def910df0bab..60f6a66e07a7b 100644 --- a/asv_bench/benchmarks/frame_ctor.py +++ b/asv_bench/benchmarks/frame_ctor.py @@ -7,13 +7,9 @@ # For compatibility with older versions from pandas.core.datetools import * # noqa -from .pandas_vb_common import setup # noqa - class FromDicts(object): - goal_time = 0.2 - def setup(self): N, K = 5000, 50 self.index = tm.makeStringIndex(N) @@ -47,8 +43,6 @@ def time_nested_dict_int64(self): class FromSeries(object): - goal_time = 0.2 - def setup(self): mi = MultiIndex.from_product([range(100), range(100)]) self.s = Series(np.random.randn(10000), index=mi) @@ -59,7 +53,6 @@ def time_mi_series(self): class FromDictwithTimestamp(object): - goal_time = 0.2 params = [Nano(1), Hour(1)] param_names = ['offset'] @@ -76,7 +69,6 @@ def time_dict_with_timestamp_offsets(self, offset): class FromRecords(object): - goal_time = 0.2 params = [None, 1000] param_names = ['nrows'] @@ -91,11 +83,12 @@ def time_frame_from_records_generator(self, nrows): class FromNDArray(object): - goal_time = 0.2 - def setup(self): N = 100000 self.data = np.random.randn(N) def time_frame_from_ndarray(self): self.df = DataFrame(self.data) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/frame_methods.py b/asv_bench/benchmarks/frame_methods.py index 4ff71c706cd34..b60b45cc29f7d 100644 --- a/asv_bench/benchmarks/frame_methods.py +++ b/asv_bench/benchmarks/frame_methods.py @@ -6,13 +6,9 @@ from pandas import (DataFrame, Series, MultiIndex, date_range, period_range, isnull, NaT) -from .pandas_vb_common import setup # noqa - class GetNumericData(object): - goal_time = 0.2 - def setup(self): self.df = DataFrame(np.random.randn(10000, 25)) self.df['foo'] = 'bar' @@ -26,8 +22,6 @@ def time_frame_get_numeric_data(self): class Lookup(object): - goal_time = 0.2 - def setup(self): self.df = DataFrame(np.random.randn(10000, 8), columns=list('abcdefgh')) @@ -48,8 +42,6 @@ def time_frame_fancy_lookup_all(self): class Reindex(object): - goal_time = 0.2 - def setup(self): N = 10**3 self.df = DataFrame(np.random.randn(N * 10, N)) @@ -79,8 +71,6 @@ def time_reindex_upcast(self): class Iteration(object): - goal_time = 0.2 - def setup(self): N = 1000 self.df = DataFrame(np.random.randn(N * 10, N)) @@ -114,8 +104,6 @@ def time_iterrows(self): class ToString(object): - goal_time = 0.2 - def setup(self): self.df = DataFrame(np.random.randn(100, 10)) @@ -125,8 +113,6 @@ def time_to_string_floats(self): class ToHTML(object): - goal_time = 0.2 - def setup(self): nrows = 500 self.df2 = DataFrame(np.random.randn(nrows, 10)) @@ -139,8 +125,6 @@ def time_to_html_mixed(self): class Repr(object): - goal_time = 0.2 - def setup(self): nrows = 10000 data = np.random.randn(nrows, 10) @@ -166,8 +150,6 @@ def time_frame_repr_wide(self): class MaskBool(object): - goal_time = 0.2 - def setup(self): data = np.random.randn(1000, 500) df = DataFrame(data) @@ -184,8 +166,6 @@ def time_frame_mask_floats(self): class Isnull(object): - goal_time = 0.2 - def setup(self): N = 10**3 self.df_no_null = DataFrame(np.random.randn(N, N)) @@ -218,7 +198,6 @@ def time_isnull_obj(self): class Fillna(object): - goal_time = 0.2 params = ([True, False], ['pad', 'bfill']) param_names = ['inplace', 'method'] @@ -233,7 +212,6 @@ def time_frame_fillna(self, inplace, method): class Dropna(object): - goal_time = 0.2 params = (['all', 'any'], [0, 1]) param_names = ['how', 'axis'] @@ -254,8 +232,6 @@ def time_dropna_axis_mixed_dtypes(self, how, axis): class Count(object): - goal_time = 0.2 - params = [0, 1] param_names = ['axis'] @@ -284,8 +260,6 @@ def time_count_level_mixed_dtypes_multi(self, axis): class Apply(object): - goal_time = 0.2 - def setup(self): self.df = DataFrame(np.random.randn(1000, 100)) @@ -314,8 +288,6 @@ def time_apply_ref_by_name(self): class Dtypes(object): - goal_time = 0.2 - def setup(self): self.df = DataFrame(np.random.randn(1000, 1000)) @@ -325,8 +297,6 @@ def time_frame_dtypes(self): class Equals(object): - goal_time = 0.2 - def setup(self): N = 10**3 self.float_df = DataFrame(np.random.randn(N, N)) @@ -363,7 +333,6 @@ def time_frame_object_unequal(self): class Interpolate(object): - goal_time = 0.2 params = [None, 'infer'] param_names = ['downcast'] @@ -389,7 +358,6 @@ def time_interpolate_some_good(self, downcast): class Shift(object): # frame shift speedup issue-5609 - goal_time = 0.2 params = [0, 1] param_names = ['axis'] @@ -411,8 +379,6 @@ def time_frame_nunique(self): class Duplicated(object): - goal_time = 0.2 - def setup(self): n = (1 << 20) t = date_range('2015-01-01', freq='S', periods=(n // 64)) @@ -431,7 +397,6 @@ def time_frame_duplicated_wide(self): class XS(object): - goal_time = 0.2 params = [0, 1] param_names = ['axis'] @@ -445,7 +410,6 @@ def time_frame_xs(self, axis): class SortValues(object): - goal_time = 0.2 params = [True, False] param_names = ['ascending'] @@ -458,8 +422,6 @@ def time_frame_sort_values(self, ascending): class SortIndexByColumns(object): - goal_time = 0.2 - def setup(self): N = 10000 K = 10 @@ -473,7 +435,6 @@ def time_frame_sort_values_by_columns(self): class Quantile(object): - goal_time = 0.2 params = [0, 1] param_names = ['axis'] @@ -486,8 +447,6 @@ def time_frame_quantile(self, axis): class GetDtypeCounts(object): # 2807 - goal_time = 0.2 - def setup(self): self.df = DataFrame(np.random.randn(10, 10000)) @@ -500,15 +459,40 @@ def time_info(self): class NSort(object): - goal_time = 0.2 - params = ['first', 'last'] + params = ['first', 'last', 'all'] param_names = ['keep'] def setup(self, keep): - self.df = DataFrame(np.random.randn(1000, 3), columns=list('ABC')) + self.df = DataFrame(np.random.randn(100000, 3), + columns=list('ABC')) - def time_nlargest(self, keep): + def time_nlargest_one_column(self, keep): self.df.nlargest(100, 'A', keep=keep) - def time_nsmallest(self, keep): + def time_nlargest_two_columns(self, keep): + self.df.nlargest(100, ['A', 'B'], keep=keep) + + def time_nsmallest_one_column(self, keep): self.df.nsmallest(100, 'A', keep=keep) + + def time_nsmallest_two_columns(self, keep): + self.df.nsmallest(100, ['A', 'B'], keep=keep) + + +class Describe(object): + + def setup(self): + self.df = DataFrame({ + 'a': np.random.randint(0, 100, int(1e6)), + 'b': np.random.randint(0, 100, int(1e6)), + 'c': np.random.randint(0, 100, int(1e6)) + }) + + def time_series_describe(self): + self.df['a'].describe() + + def time_dataframe_describe(self): + self.df.describe() + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/gil.py b/asv_bench/benchmarks/gil.py index 21c1ccf46e1c4..6819a296c81df 100644 --- a/asv_bench/benchmarks/gil.py +++ b/asv_bench/benchmarks/gil.py @@ -23,12 +23,11 @@ def wrapper(fname): return fname return wrapper -from .pandas_vb_common import BaseIO, setup # noqa +from .pandas_vb_common import BaseIO class ParallelGroupbyMethods(object): - goal_time = 0.2 params = ([2, 4, 8], ['count', 'last', 'max', 'mean', 'min', 'prod', 'sum', 'var']) param_names = ['threads', 'method'] @@ -60,7 +59,6 @@ def time_loop(self, threads, method): class ParallelGroups(object): - goal_time = 0.2 params = [2, 4, 8] param_names = ['threads'] @@ -82,7 +80,6 @@ def time_get_groups(self, threads): class ParallelTake1D(object): - goal_time = 0.2 params = ['int64', 'float64'] param_names = ['dtype'] @@ -126,8 +123,6 @@ def time_kth_smallest(self): class ParallelDatetimeFields(object): - goal_time = 0.2 - def setup(self): if not have_real_test_parallel: raise NotImplementedError @@ -174,7 +169,6 @@ def run(period): class ParallelRolling(object): - goal_time = 0.2 params = ['median', 'mean', 'min', 'max', 'var', 'skew', 'kurt', 'std'] param_names = ['method'] @@ -273,3 +267,6 @@ def time_parallel(self, threads): def time_loop(self, threads): for i in range(threads): self.loop() + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/groupby.py b/asv_bench/benchmarks/groupby.py index 7777322071957..dbd79185ec006 100644 --- a/asv_bench/benchmarks/groupby.py +++ b/asv_bench/benchmarks/groupby.py @@ -5,11 +5,9 @@ import numpy as np from pandas import (DataFrame, Series, MultiIndex, date_range, period_range, - TimeGrouper, Categorical) + TimeGrouper, Categorical, Timestamp) import pandas.util.testing as tm -from .pandas_vb_common import setup # noqa - method_blacklist = { 'object': {'median', 'prod', 'sem', 'cumsum', 'sum', 'cummin', 'mean', @@ -22,8 +20,6 @@ class ApplyDictReturn(object): - goal_time = 0.2 - def setup(self): self.labels = np.arange(1000).repeat(10) self.data = Series(np.random.randn(len(self.labels))) @@ -35,8 +31,6 @@ def time_groupby_apply_dict_return(self): class Apply(object): - goal_time = 0.2 - def setup_cache(self): N = 10**4 labels = np.random.randint(0, 2000, size=N) @@ -69,8 +63,6 @@ def time_copy_overhead_single_col(self, df): class Groups(object): - goal_time = 0.2 - param_names = ['key'] params = ['int64_small', 'int64_large', 'object_small', 'object_large'] @@ -95,7 +87,6 @@ def time_series_groups(self, data, key): class GroupManyLabels(object): - goal_time = 0.2 params = [1, 1000] param_names = ['ncols'] @@ -111,8 +102,6 @@ def time_sum(self, ncols): class Nth(object): - goal_time = 0.2 - param_names = ['dtype'] params = ['float32', 'float64', 'datetime', 'object'] @@ -142,7 +131,7 @@ def time_frame_nth(self, dtype): def time_series_nth_any(self, dtype): self.df['values'].groupby(self.df['key']).nth(0, dropna='any') - def time_groupby_nth_all(self, dtype): + def time_series_nth_all(self, dtype): self.df['values'].groupby(self.df['key']).nth(0, dropna='all') def time_series_nth(self, dtype): @@ -151,8 +140,6 @@ def time_series_nth(self, dtype): class DateAttributes(object): - goal_time = 0.2 - def setup(self): rng = date_range('1/1/2000', '12/31/2005', freq='H') self.year, self.month, self.day = rng.year, rng.month, rng.day @@ -164,8 +151,6 @@ def time_len_groupby_object(self): class Int64(object): - goal_time = 0.2 - def setup(self): arr = np.random.randint(-1 << 12, 1 << 12, (1 << 17, 5)) i = np.random.choice(len(arr), len(arr) * 5) @@ -182,8 +167,6 @@ def time_overflow(self): class CountMultiDtype(object): - goal_time = 0.2 - def setup_cache(self): n = 10000 offsets = np.random.randint(n, size=n).astype('timedelta64[ns]') @@ -210,8 +193,6 @@ def time_multi_count(self, df): class CountMultiInt(object): - goal_time = 0.2 - def setup_cache(self): n = 10000 df = DataFrame({'key1': np.random.randint(0, 500, size=n), @@ -229,8 +210,6 @@ def time_multi_int_nunique(self, df): class AggFunctions(object): - goal_time = 0.2 - def setup_cache(): N = 10**5 fac1 = np.array(['A', 'B', 'C'], dtype='O') @@ -261,8 +240,6 @@ def time_different_python_functions_singlecol(self, df): class GroupStrings(object): - goal_time = 0.2 - def setup(self): n = 2 * 10**5 alpha = list(map(''.join, product(ascii_letters, repeat=4))) @@ -278,8 +255,6 @@ def time_multi_columns(self): class MultiColumn(object): - goal_time = 0.2 - def setup_cache(self): N = 10**5 key1 = np.tile(np.arange(100, dtype=object), 1000) @@ -307,8 +282,6 @@ def time_col_select_numpy_sum(self, df): class Size(object): - goal_time = 0.2 - def setup(self): n = 10**5 offsets = np.random.randint(n, size=n).astype('timedelta64[ns]') @@ -336,8 +309,6 @@ def time_category_size(self): class GroupByMethods(object): - goal_time = 0.2 - param_names = ['dtype', 'method', 'application'] params = [['int', 'float', 'object', 'datetime'], ['all', 'any', 'bfill', 'count', 'cumcount', 'cummax', 'cummin', @@ -385,10 +356,26 @@ def time_dtype_as_field(self, dtype, method, application): self.as_field_method() +class RankWithTies(object): + # GH 21237 + param_names = ['dtype', 'tie_method'] + params = [['float64', 'float32', 'int64', 'datetime64'], + ['first', 'average', 'dense', 'min', 'max']] + + def setup(self, dtype, tie_method): + N = 10**4 + if dtype == 'datetime64': + data = np.array([Timestamp("2011/01/01")] * N, dtype=dtype) + else: + data = np.array([1] * N, dtype=dtype) + self.df = DataFrame({'values': data, 'key': ['foo'] * N}) + + def time_rank_ties(self, dtype, tie_method): + self.df.groupby('key').rank(method=tie_method) + + class Float32(object): # GH 13335 - goal_time = 0.2 - def setup(self): tmp1 = (np.random.random(10000) * 0.1).astype(np.float32) tmp2 = (np.random.random(10000) * 10.0).astype(np.float32) @@ -402,8 +389,6 @@ def time_sum(self): class Categories(object): - goal_time = 0.2 - def setup(self): N = 10**5 arr = np.random.random(N) @@ -440,7 +425,6 @@ def time_groupby_extra_cat_nosort(self): class Datelike(object): # GH 14338 - goal_time = 0.2 params = ['period_range', 'date_range', 'date_range_tz'] param_names = ['grouper'] @@ -458,8 +442,6 @@ def time_sum(self, grouper): class SumBools(object): # GH 2692 - goal_time = 0.2 - def setup(self): N = 500 self.df = DataFrame({'ii': range(N), @@ -471,7 +453,6 @@ def time_groupby_sum_booleans(self): class SumMultiLevel(object): # GH 9049 - goal_time = 0.2 timeout = 120.0 def setup(self): @@ -486,8 +467,6 @@ def time_groupby_sum_multiindex(self): class Transform(object): - goal_time = 0.2 - def setup(self): n1 = 400 n2 = 250 @@ -534,8 +513,6 @@ def time_transform_multi_key4(self): class TransformBools(object): - goal_time = 0.2 - def setup(self): N = 120000 transition_points = np.sort(np.random.choice(np.arange(N), 1400)) @@ -550,8 +527,6 @@ def time_transform_mean(self): class TransformNaN(object): # GH 12737 - goal_time = 0.2 - def setup(self): self.df_nans = DataFrame({'key': np.repeat(np.arange(1000), 10), 'B': np.nan, @@ -560,3 +535,6 @@ def setup(self): def time_first(self): self.df_nans.groupby('key').transform('first') + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/index_object.py b/asv_bench/benchmarks/index_object.py index f1703e163917a..f76040921393f 100644 --- a/asv_bench/benchmarks/index_object.py +++ b/asv_bench/benchmarks/index_object.py @@ -3,12 +3,9 @@ from pandas import (Series, date_range, DatetimeIndex, Index, RangeIndex, Float64Index) -from .pandas_vb_common import setup # noqa - class SetOperations(object): - goal_time = 0.2 params = (['datetime', 'date_string', 'int', 'strings'], ['intersection', 'union', 'symmetric_difference']) param_names = ['dtype', 'method'] @@ -34,8 +31,6 @@ def time_operation(self, dtype, method): class SetDisjoint(object): - goal_time = 0.2 - def setup(self): N = 10**5 B = N + 20000 @@ -48,8 +43,6 @@ def time_datetime_difference_disjoint(self): class Datetime(object): - goal_time = 0.2 - def setup(self): self.dr = date_range('20000101', freq='D', periods=10000) @@ -86,8 +79,6 @@ def time_modulo(self, dtype): class Range(object): - goal_time = 0.2 - def setup(self): self.idx_inc = RangeIndex(start=0, stop=10**7, step=3) self.idx_dec = RangeIndex(start=10**7, stop=-1, step=-3) @@ -107,8 +98,6 @@ def time_min_trivial(self): class IndexAppend(object): - goal_time = 0.2 - def setup(self): N = 10000 @@ -138,7 +127,6 @@ def time_append_obj_list(self): class Indexing(object): - goal_time = 0.2 params = ['String', 'Float', 'Int'] param_names = ['dtype'] @@ -183,8 +171,6 @@ def time_get_loc_non_unique_sorted(self, dtype): class Float64IndexMethod(object): # GH 13166 - goal_time = 0.2 - def setup(self): N = 100000 a = np.arange(N) @@ -192,3 +178,6 @@ def setup(self): def time_get_loc(self): self.ind.get_loc(0) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/indexing.py b/asv_bench/benchmarks/indexing.py index 77e013e1e4fb0..57ba9cd80e55c 100644 --- a/asv_bench/benchmarks/indexing.py +++ b/asv_bench/benchmarks/indexing.py @@ -2,107 +2,119 @@ import numpy as np import pandas.util.testing as tm -from pandas import (Series, DataFrame, MultiIndex, Int64Index, Float64Index, - IntervalIndex, IndexSlice, concat, date_range) -from .pandas_vb_common import setup, Panel # noqa +from pandas import (Series, DataFrame, Panel, MultiIndex, + Int64Index, UInt64Index, Float64Index, + IntervalIndex, CategoricalIndex, + IndexSlice, concat, date_range) class NumericSeriesIndexing(object): - goal_time = 0.2 - params = [Int64Index, Float64Index] - param = ['index'] + params = [ + (Int64Index, UInt64Index, Float64Index), + ('unique_monotonic_inc', 'nonunique_monotonic_inc'), + ] + param_names = ['index_dtype', 'index_structure'] - def setup(self, index): + def setup(self, index, index_structure): N = 10**6 - idx = index(range(N)) - self.data = Series(np.random.rand(N), index=idx) + indices = { + 'unique_monotonic_inc': index(range(N)), + 'nonunique_monotonic_inc': index( + list(range(55)) + [54] + list(range(55, N - 1))), + } + self.data = Series(np.random.rand(N), index=indices[index_structure]) self.array = np.arange(10000) self.array_list = self.array.tolist() - def time_getitem_scalar(self, index): + def time_getitem_scalar(self, index, index_structure): self.data[800000] - def time_getitem_slice(self, index): + def time_getitem_slice(self, index, index_structure): self.data[:800000] - def time_getitem_list_like(self, index): + def time_getitem_list_like(self, index, index_structure): self.data[[800000]] - def time_getitem_array(self, index): + def time_getitem_array(self, index, index_structure): self.data[self.array] - def time_getitem_lists(self, index): + def time_getitem_lists(self, index, index_structure): self.data[self.array_list] - def time_iloc_array(self, index): + def time_iloc_array(self, index, index_structure): self.data.iloc[self.array] - def time_iloc_list_like(self, index): + def time_iloc_list_like(self, index, index_structure): self.data.iloc[[800000]] - def time_iloc_scalar(self, index): + def time_iloc_scalar(self, index, index_structure): self.data.iloc[800000] - def time_iloc_slice(self, index): + def time_iloc_slice(self, index, index_structure): self.data.iloc[:800000] - def time_ix_array(self, index): + def time_ix_array(self, index, index_structure): self.data.ix[self.array] - def time_ix_list_like(self, index): + def time_ix_list_like(self, index, index_structure): self.data.ix[[800000]] - def time_ix_scalar(self, index): + def time_ix_scalar(self, index, index_structure): self.data.ix[800000] - def time_ix_slice(self, index): + def time_ix_slice(self, index, index_structure): self.data.ix[:800000] - def time_loc_array(self, index): + def time_loc_array(self, index, index_structure): self.data.loc[self.array] - def time_loc_list_like(self, index): + def time_loc_list_like(self, index, index_structure): self.data.loc[[800000]] - def time_loc_scalar(self, index): + def time_loc_scalar(self, index, index_structure): self.data.loc[800000] - def time_loc_slice(self, index): + def time_loc_slice(self, index, index_structure): self.data.loc[:800000] class NonNumericSeriesIndexing(object): - goal_time = 0.2 - params = ['string', 'datetime'] - param_names = ['index'] + params = [ + ('string', 'datetime'), + ('unique_monotonic_inc', 'nonunique_monotonic_inc'), + ] + param_names = ['index_dtype', 'index_structure'] - def setup(self, index): - N = 10**5 + def setup(self, index, index_structure): + N = 10**6 indexes = {'string': tm.makeStringIndex(N), 'datetime': date_range('1900', periods=N, freq='s')} index = indexes[index] + if index_structure == 'nonunique_monotonic_inc': + index = index.insert(item=index[2], loc=2)[:-1] self.s = Series(np.random.rand(N), index=index) self.lbl = index[80000] - def time_getitem_label_slice(self, index): + def time_getitem_label_slice(self, index, index_structure): self.s[:self.lbl] - def time_getitem_pos_slice(self, index): + def time_getitem_pos_slice(self, index, index_structure): self.s[:80000] - def time_get_value(self, index): + def time_get_value(self, index, index_structure): with warnings.catch_warnings(record=True): self.s.get_value(self.lbl) - def time_getitem_scalar(self, index): + def time_getitem_scalar(self, index, index_structure): self.s[self.lbl] + def time_getitem_list_like(self, index, index_structure): + self.s[[self.lbl]] -class DataFrameStringIndexing(object): - goal_time = 0.2 +class DataFrameStringIndexing(object): def setup(self): index = tm.makeStringIndex(1000) @@ -136,8 +148,6 @@ def time_boolean_rows_object(self): class DataFrameNumericIndexing(object): - goal_time = 0.2 - def setup(self): self.idx_dupe = np.array(range(30)) * 99 self.df = DataFrame(np.random.randn(10000, 5)) @@ -162,7 +172,6 @@ def time_bool_indexer(self): class Take(object): - goal_time = 0.2 params = ['int', 'datetime'] param_names = ['index'] @@ -180,8 +189,6 @@ def time_take(self, index): class MultiIndexing(object): - goal_time = 0.2 - def setup(self): mi = MultiIndex.from_product([range(1000), range(1000)]) self.s = Series(np.random.randn(1000000), index=mi) @@ -210,8 +217,6 @@ def time_index_slice(self): class IntervalIndexing(object): - goal_time = 0.2 - def setup_cache(self): idx = IntervalIndex.from_breaks(np.arange(1000001)) monotonic = Series(np.arange(1000000), index=idx) @@ -230,9 +235,49 @@ def time_loc_list(self, monotonic): monotonic.loc[80000:] -class PanelIndexing(object): +class CategoricalIndexIndexing(object): + + params = ['monotonic_incr', 'monotonic_decr', 'non_monotonic'] + param_names = ['index'] + + def setup(self, index): + N = 10**5 + values = list('a' * N + 'b' * N + 'c' * N) + indices = { + 'monotonic_incr': CategoricalIndex(values), + 'monotonic_decr': CategoricalIndex(reversed(values)), + 'non_monotonic': CategoricalIndex(list('abc' * N))} + self.data = indices[index] - goal_time = 0.2 + self.int_scalar = 10000 + self.int_list = list(range(10000)) + + self.cat_scalar = 'b' + self.cat_list = ['a', 'c'] + + def time_getitem_scalar(self, index): + self.data[self.int_scalar] + + def time_getitem_slice(self, index): + self.data[:self.int_scalar] + + def time_getitem_list_like(self, index): + self.data[[self.int_scalar]] + + def time_getitem_list(self, index): + self.data[self.int_list] + + def time_getitem_bool_array(self, index): + self.data[self.data == self.cat_scalar] + + def time_get_loc_scalar(self, index): + self.data.get_loc(self.cat_scalar) + + def time_get_indexer_list(self, index): + self.data.get_indexer(self.cat_list) + + +class PanelIndexing(object): def setup(self): with warnings.catch_warnings(record=True): @@ -246,8 +291,6 @@ def time_subset(self): class MethodLookup(object): - goal_time = 0.2 - def setup_cache(self): s = Series() return s @@ -264,8 +307,6 @@ def time_lookup_loc(self, s): class GetItemSingleColumn(object): - goal_time = 0.2 - def setup(self): self.df_string_col = DataFrame(np.random.randn(3000, 1), columns=['A']) self.df_int_col = DataFrame(np.random.randn(3000, 1)) @@ -279,8 +320,6 @@ def time_frame_getitem_single_column_int(self): class AssignTimeseriesIndex(object): - goal_time = 0.2 - def setup(self): N = 100000 idx = date_range('1/1/2000', periods=N, freq='H') @@ -292,8 +331,6 @@ def time_frame_assign_timeseries_index(self): class InsertColumns(object): - goal_time = 0.2 - def setup(self): self.N = 10**3 self.df = DataFrame(index=range(self.N)) @@ -308,3 +345,6 @@ def time_assign_with_setitem(self): np.random.seed(1234) for i in range(100): self.df[i] = np.random.randn(self.N) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/indexing_engines.py b/asv_bench/benchmarks/indexing_engines.py new file mode 100644 index 0000000000000..f3d063ee31bc8 --- /dev/null +++ b/asv_bench/benchmarks/indexing_engines.py @@ -0,0 +1,64 @@ +import numpy as np + +from pandas._libs import index as libindex + + +def _get_numeric_engines(): + engine_names = [ + ('Int64Engine', np.int64), ('Int32Engine', np.int32), + ('Int16Engine', np.int16), ('Int8Engine', np.int8), + ('UInt64Engine', np.uint64), ('UInt32Engine', np.uint32), + ('UInt16engine', np.uint16), ('UInt8Engine', np.uint8), + ('Float64Engine', np.float64), ('Float32Engine', np.float32), + ] + return [(getattr(libindex, engine_name), dtype) + for engine_name, dtype in engine_names + if hasattr(libindex, engine_name)] + + +class NumericEngineIndexing(object): + + params = [_get_numeric_engines(), + ['monotonic_incr', 'monotonic_decr', 'non_monotonic'], + ] + param_names = ['engine_and_dtype', 'index_type'] + + def setup(self, engine_and_dtype, index_type): + engine, dtype = engine_and_dtype + N = 10**5 + values = list([1] * N + [2] * N + [3] * N) + arr = { + 'monotonic_incr': np.array(values, dtype=dtype), + 'monotonic_decr': np.array(list(reversed(values)), + dtype=dtype), + 'non_monotonic': np.array([1, 2, 3] * N, dtype=dtype), + }[index_type] + + self.data = engine(lambda: arr, len(arr)) + # code belows avoids populating the mapping etc. while timing. + self.data.get_loc(2) + + def time_get_loc(self, engine_and_dtype, index_type): + self.data.get_loc(2) + + +class ObjectEngineIndexing(object): + + params = [('monotonic_incr', 'monotonic_decr', 'non_monotonic')] + param_names = ['index_type'] + + def setup(self, index_type): + N = 10**5 + values = list('a' * N + 'b' * N + 'c' * N) + arr = { + 'monotonic_incr': np.array(values, dtype=object), + 'monotonic_decr': np.array(list(reversed(values)), dtype=object), + 'non_monotonic': np.array(list('abc') * N, dtype=object), + }[index_type] + + self.data = libindex.ObjectEngine(lambda: arr, len(arr)) + # code belows avoids populating the mapping etc. while timing. + self.data.get_loc('b') + + def time_get_loc(self, index_type): + self.data.get_loc('b') diff --git a/asv_bench/benchmarks/inference.py b/asv_bench/benchmarks/inference.py index 16d9e7cd73cbb..423bd02b93596 100644 --- a/asv_bench/benchmarks/inference.py +++ b/asv_bench/benchmarks/inference.py @@ -2,12 +2,11 @@ import pandas.util.testing as tm from pandas import DataFrame, Series, to_numeric -from .pandas_vb_common import numeric_dtypes, lib, setup # noqa +from .pandas_vb_common import numeric_dtypes, lib class NumericInferOps(object): # from GH 7332 - goal_time = 0.2 params = numeric_dtypes param_names = ['dtype'] @@ -34,8 +33,6 @@ def time_modulo(self, dtype): class DateInferOps(object): # from GH 7332 - goal_time = 0.2 - def setup_cache(self): N = 5 * 10**5 df = DataFrame({'datetime64': np.arange(N).astype('datetime64[ms]')}) @@ -54,7 +51,6 @@ def time_add_timedeltas(self, df): class ToNumeric(object): - goal_time = 0.2 params = ['ignore', 'coerce'] param_names = ['errors'] @@ -111,3 +107,6 @@ def setup_cache(self): def time_convert(self, data): lib.maybe_convert_numeric(data, set(), coerce_numeric=False) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/io/csv.py b/asv_bench/benchmarks/io/csv.py index 0f5d07f9fac55..771f2795334e1 100644 --- a/asv_bench/benchmarks/io/csv.py +++ b/asv_bench/benchmarks/io/csv.py @@ -1,19 +1,16 @@ import random -import timeit import string import numpy as np import pandas.util.testing as tm from pandas import DataFrame, Categorical, date_range, read_csv -from pandas.compat import PY2 from pandas.compat import cStringIO as StringIO -from ..pandas_vb_common import setup, BaseIO # noqa +from ..pandas_vb_common import BaseIO class ToCSV(BaseIO): - goal_time = 0.2 fname = '__test__.csv' params = ['wide', 'long', 'mixed'] param_names = ['kind'] @@ -43,7 +40,6 @@ def time_frame(self, kind): class ToCSVDatetime(BaseIO): - goal_time = 0.2 fname = '__test__.csv' def setup(self): @@ -54,9 +50,15 @@ def time_frame_date_formatting(self): self.data.to_csv(self.fname, date_format='%Y%m%d') -class ReadCSVDInferDatetimeFormat(object): +class StringIORewind(object): + + def data(self, stringio_object): + stringio_object.seek(0) + return stringio_object + + +class ReadCSVDInferDatetimeFormat(StringIORewind): - goal_time = 0.2 params = ([True, False], ['custom', 'iso8601', 'ymd']) param_names = ['infer_datetime_format', 'format'] @@ -66,16 +68,17 @@ def setup(self, infer_datetime_format, format): 'iso8601': '%Y-%m-%d %H:%M:%S', 'ymd': '%Y%m%d'} dt_format = formats[format] - self.data = StringIO('\n'.join(rng.strftime(dt_format).tolist())) + self.StringIO_input = StringIO('\n'.join( + rng.strftime(dt_format).tolist())) def time_read_csv(self, infer_datetime_format, format): - read_csv(self.data, header=None, names=['foo'], parse_dates=['foo'], + read_csv(self.data(self.StringIO_input), + header=None, names=['foo'], parse_dates=['foo'], infer_datetime_format=infer_datetime_format) class ReadCSVSkipRows(BaseIO): - goal_time = 0.2 fname = '__test__.csv' params = [None, 10000] param_names = ['skiprows'] @@ -95,9 +98,7 @@ def time_skipprows(self, skiprows): read_csv(self.fname, skiprows=skiprows) -class ReadUint64Integers(object): - - goal_time = 0.2 +class ReadUint64Integers(StringIORewind): def setup(self): self.na_values = [2**63 + 500] @@ -108,19 +109,18 @@ def setup(self): self.data2 = StringIO('\n'.join(arr.astype(str).tolist())) def time_read_uint64(self): - read_csv(self.data1, header=None, names=['foo']) + read_csv(self.data(self.data1), header=None, names=['foo']) def time_read_uint64_neg_values(self): - read_csv(self.data2, header=None, names=['foo']) + read_csv(self.data(self.data2), header=None, names=['foo']) def time_read_uint64_na_values(self): - read_csv(self.data1, header=None, names=['foo'], + read_csv(self.data(self.data1), header=None, names=['foo'], na_values=self.na_values) class ReadCSVThousands(BaseIO): - goal_time = 0.2 fname = '__test__.csv' params = ([',', '|'], [None, ',']) param_names = ['sep', 'thousands'] @@ -140,21 +140,19 @@ def time_thousands(self, sep, thousands): read_csv(self.fname, sep=sep, thousands=thousands) -class ReadCSVComment(object): - - goal_time = 0.2 +class ReadCSVComment(StringIORewind): def setup(self): data = ['A,B,C'] + (['1,2,3 # comment'] * 100000) - self.s_data = StringIO('\n'.join(data)) + self.StringIO_input = StringIO('\n'.join(data)) def time_comment(self): - read_csv(self.s_data, comment='#', header=None, names=list('abc')) + read_csv(self.data(self.StringIO_input), comment='#', + header=None, names=list('abc')) -class ReadCSVFloatPrecision(object): +class ReadCSVFloatPrecision(StringIORewind): - goal_time = 0.2 params = ([',', ';'], ['.', '_'], [None, 'high', 'round_trip']) param_names = ['sep', 'decimal', 'float_precision'] @@ -164,20 +162,19 @@ def setup(self, sep, decimal, float_precision): rows = sep.join(['0{}'.format(decimal) + '{}'] * 3) + '\n' data = rows * 5 data = data.format(*floats) * 200 # 1000 x 3 strings csv - self.s_data = StringIO(data) + self.StringIO_input = StringIO(data) def time_read_csv(self, sep, decimal, float_precision): - read_csv(self.s_data, sep=sep, header=None, names=list('abc'), - float_precision=float_precision) + read_csv(self.data(self.StringIO_input), sep=sep, header=None, + names=list('abc'), float_precision=float_precision) def time_read_csv_python_engine(self, sep, decimal, float_precision): - read_csv(self.s_data, sep=sep, header=None, engine='python', - float_precision=None, names=list('abc')) + read_csv(self.data(self.StringIO_input), sep=sep, header=None, + engine='python', float_precision=None, names=list('abc')) class ReadCSVCategorical(BaseIO): - goal_time = 0.2 fname = '__test__.csv' def setup(self): @@ -193,9 +190,7 @@ def time_convert_direct(self): read_csv(self.fname, dtype='category') -class ReadCSVParseDates(object): - - goal_time = 0.2 +class ReadCSVParseDates(StringIORewind): def setup(self): data = """{},19:00:00,18:56:00,0.8100,2.8100,7.2000,0.0000,280.0000\n @@ -206,12 +201,17 @@ def setup(self): """ two_cols = ['KORD,19990127'] * 5 data = data.format(*two_cols) - self.s_data = StringIO(data) + self.StringIO_input = StringIO(data) def time_multiple_date(self): - read_csv(self.s_data, sep=',', header=None, - names=list(string.digits[:9]), parse_dates=[[1, 2], [1, 3]]) + read_csv(self.data(self.StringIO_input), sep=',', header=None, + names=list(string.digits[:9]), + parse_dates=[[1, 2], [1, 3]]) def time_baseline(self): - read_csv(self.s_data, sep=',', header=None, parse_dates=[1], + read_csv(self.data(self.StringIO_input), sep=',', header=None, + parse_dates=[1], names=list(string.digits[:9])) + + +from ..pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/io/excel.py b/asv_bench/benchmarks/io/excel.py index 58ab6bb8046c5..1bee864fbcf2d 100644 --- a/asv_bench/benchmarks/io/excel.py +++ b/asv_bench/benchmarks/io/excel.py @@ -3,12 +3,9 @@ from pandas.compat import BytesIO import pandas.util.testing as tm -from ..pandas_vb_common import BaseIO, setup # noqa - class Excel(object): - goal_time = 0.2 params = ['openpyxl', 'xlsxwriter', 'xlwt'] param_names = ['engine'] @@ -34,3 +31,6 @@ def time_write_excel(self, engine): writer_write = ExcelWriter(bio_write, engine=engine) self.df.to_excel(writer_write, sheet_name='Sheet1') writer_write.save() + + +from ..pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/io/hdf.py b/asv_bench/benchmarks/io/hdf.py index 4b6e1d69af92d..f08904ba70a5f 100644 --- a/asv_bench/benchmarks/io/hdf.py +++ b/asv_bench/benchmarks/io/hdf.py @@ -4,13 +4,11 @@ from pandas import DataFrame, Panel, date_range, HDFStore, read_hdf import pandas.util.testing as tm -from ..pandas_vb_common import BaseIO, setup # noqa +from ..pandas_vb_common import BaseIO class HDFStoreDataFrame(BaseIO): - goal_time = 0.2 - def setup(self): N = 25000 index = tm.makeStringIndex(N) @@ -103,8 +101,6 @@ def time_store_info(self): class HDFStorePanel(BaseIO): - goal_time = 0.2 - def setup(self): self.fname = '__test__.h5' with warnings.catch_warnings(record=True): @@ -130,7 +126,6 @@ def time_write_store_table_panel(self): class HDF(BaseIO): - goal_time = 0.2 params = ['table', 'fixed'] param_names = ['format'] @@ -149,3 +144,6 @@ def time_read_hdf(self, format): def time_write_hdf(self, format): self.df.to_hdf(self.fname, 'df', format=format) + + +from ..pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/io/json.py b/asv_bench/benchmarks/io/json.py index acfdd327c3b51..ec2ddc11b7c1d 100644 --- a/asv_bench/benchmarks/io/json.py +++ b/asv_bench/benchmarks/io/json.py @@ -2,12 +2,11 @@ import pandas.util.testing as tm from pandas import DataFrame, date_range, timedelta_range, concat, read_json -from ..pandas_vb_common import setup, BaseIO # noqa +from ..pandas_vb_common import BaseIO class ReadJSON(BaseIO): - goal_time = 0.2 fname = "__test__.json" params = (['split', 'index', 'records'], ['int', 'datetime']) param_names = ['orient', 'index'] @@ -27,7 +26,6 @@ def time_read_json(self, orient, index): class ReadJSONLines(BaseIO): - goal_time = 0.2 fname = "__test_lines__.json" params = ['int', 'datetime'] param_names = ['index'] @@ -58,7 +56,6 @@ def peakmem_read_json_lines_concat(self, index): class ToJSON(BaseIO): - goal_time = 0.2 fname = "__test__.json" params = ['split', 'columns', 'index'] param_names = ['orient'] @@ -125,3 +122,6 @@ def time_float_int_lines(self, orient): def time_float_int_str_lines(self, orient): self.df_int_float_str.to_json(self.fname, orient='records', lines=True) + + +from ..pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/io/msgpack.py b/asv_bench/benchmarks/io/msgpack.py index 8ccce01117ca4..dc2642d920fd0 100644 --- a/asv_bench/benchmarks/io/msgpack.py +++ b/asv_bench/benchmarks/io/msgpack.py @@ -2,13 +2,11 @@ from pandas import DataFrame, date_range, read_msgpack import pandas.util.testing as tm -from ..pandas_vb_common import BaseIO, setup # noqa +from ..pandas_vb_common import BaseIO class MSGPack(BaseIO): - goal_time = 0.2 - def setup(self): self.fname = '__test__.msg' N = 100000 @@ -24,3 +22,6 @@ def time_read_msgpack(self): def time_write_msgpack(self): self.df.to_msgpack(self.fname) + + +from ..pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/io/pickle.py b/asv_bench/benchmarks/io/pickle.py index 2ad0fcca6eb26..74a58bbb946aa 100644 --- a/asv_bench/benchmarks/io/pickle.py +++ b/asv_bench/benchmarks/io/pickle.py @@ -2,13 +2,11 @@ from pandas import DataFrame, date_range, read_pickle import pandas.util.testing as tm -from ..pandas_vb_common import BaseIO, setup # noqa +from ..pandas_vb_common import BaseIO class Pickle(BaseIO): - goal_time = 0.2 - def setup(self): self.fname = '__test__.pkl' N = 100000 @@ -24,3 +22,6 @@ def time_read_pickle(self): def time_write_pickle(self): self.df.to_pickle(self.fname) + + +from ..pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/io/sas.py b/asv_bench/benchmarks/io/sas.py index 526c524de7fff..2783f42cad895 100644 --- a/asv_bench/benchmarks/io/sas.py +++ b/asv_bench/benchmarks/io/sas.py @@ -5,7 +5,6 @@ class SAS(object): - goal_time = 0.2 params = ['sas7bdat', 'xport'] param_names = ['format'] diff --git a/asv_bench/benchmarks/io/sql.py b/asv_bench/benchmarks/io/sql.py index ef4e501e5f3b9..075d3bdda5ed9 100644 --- a/asv_bench/benchmarks/io/sql.py +++ b/asv_bench/benchmarks/io/sql.py @@ -5,12 +5,9 @@ from pandas import DataFrame, date_range, read_sql_query, read_sql_table from sqlalchemy import create_engine -from ..pandas_vb_common import setup # noqa - class SQL(object): - goal_time = 0.2 params = ['sqlalchemy', 'sqlite'] param_names = ['connection'] @@ -43,7 +40,6 @@ def time_read_sql_query(self, connection): class WriteSQLDtypes(object): - goal_time = 0.2 params = (['sqlalchemy', 'sqlite'], ['float', 'float_with_nan', 'string', 'bool', 'int', 'datetime']) param_names = ['connection', 'dtype'] @@ -77,8 +73,6 @@ def time_read_sql_query_select_column(self, connection, dtype): class ReadSQLTable(object): - goal_time = 0.2 - def setup(self): N = 10000 self.table_name = 'test' @@ -106,8 +100,6 @@ def time_read_sql_table_parse_dates(self): class ReadSQLTableDtypes(object): - goal_time = 0.2 - params = ['float', 'float_with_nan', 'string', 'bool', 'int', 'datetime'] param_names = ['dtype'] @@ -130,3 +122,6 @@ def setup(self, dtype): def time_read_sql_table_column(self, dtype): read_sql_table(self.table_name, self.con, columns=[dtype]) + + +from ..pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/io/stata.py b/asv_bench/benchmarks/io/stata.py index e0f5752ca930f..a7f854a853f50 100644 --- a/asv_bench/benchmarks/io/stata.py +++ b/asv_bench/benchmarks/io/stata.py @@ -2,12 +2,11 @@ from pandas import DataFrame, date_range, read_stata import pandas.util.testing as tm -from ..pandas_vb_common import BaseIO, setup # noqa +from ..pandas_vb_common import BaseIO class Stata(BaseIO): - goal_time = 0.2 params = ['tc', 'td', 'tm', 'tw', 'th', 'tq', 'ty'] param_names = ['convert_dates'] @@ -35,3 +34,6 @@ def time_read_stata(self, convert_dates): def time_write_stata(self, convert_dates): self.df.to_stata(self.fname, self.convert_dates) + + +from ..pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/join_merge.py b/asv_bench/benchmarks/join_merge.py index de0a3b33da147..5b28d8a4eec62 100644 --- a/asv_bench/benchmarks/join_merge.py +++ b/asv_bench/benchmarks/join_merge.py @@ -3,20 +3,17 @@ import numpy as np import pandas.util.testing as tm -from pandas import (DataFrame, Series, MultiIndex, date_range, concat, merge, - merge_asof) +from pandas import (DataFrame, Series, Panel, MultiIndex, + date_range, concat, merge, merge_asof) + try: from pandas import merge_ordered except ImportError: from pandas import ordered_merge as merge_ordered -from .pandas_vb_common import Panel, setup # noqa - class Append(object): - goal_time = 0.2 - def setup(self): self.df1 = DataFrame(np.random.randn(10000, 4), columns=['A', 'B', 'C', 'D']) @@ -29,7 +26,7 @@ def setup(self): try: with warnings.catch_warnings(record=True): self.mdf1.consolidate(inplace=True) - except: + except (AttributeError, TypeError): pass self.mdf2 = self.mdf1.copy() self.mdf2.index = self.df2.index @@ -43,7 +40,6 @@ def time_append_mixed(self): class Concat(object): - goal_time = 0.2 params = [0, 1] param_names = ['axis'] @@ -72,7 +68,6 @@ def time_concat_empty_left(self, axis): class ConcatPanels(object): - goal_time = 0.2 params = ([0, 1, 2], [True, False]) param_names = ['axis', 'ignore_index'] @@ -98,7 +93,6 @@ def time_f_ordered(self, axis, ignore_index): class ConcatDataFrames(object): - goal_time = 0.2 params = ([0, 1], [True, False]) param_names = ['axis', 'ignore_index'] @@ -119,7 +113,6 @@ def time_f_ordered(self, axis, ignore_index): class Join(object): - goal_time = 0.2 params = [True, False] param_names = ['sort'] @@ -167,8 +160,6 @@ def time_join_dataframe_index_shuffle_key_bigger_sort(self, sort): class JoinIndex(object): - goal_time = 0.2 - def setup(self): N = 50000 self.left = DataFrame(np.random.randint(1, N / 500, (N, 2)), @@ -183,8 +174,6 @@ def time_left_outer_join_index(self): class JoinNonUnique(object): # outer join of non-unique # GH 6329 - goal_time = 0.2 - def setup(self): date_index = date_range('01-Jan-2013', '23-Jan-2013', freq='T') daily_dates = date_index.to_period('D').to_timestamp('S', 'S') @@ -201,7 +190,6 @@ def time_join_non_unique_equal(self): class Merge(object): - goal_time = 0.2 params = [True, False] param_names = ['sort'] @@ -236,7 +224,6 @@ def time_merge_dataframe_integer_key(self, sort): class I8Merge(object): - goal_time = 0.2 params = ['inner', 'outer', 'left', 'right'] param_names = ['how'] @@ -255,8 +242,6 @@ def time_i8merge(self, how): class MergeCategoricals(object): - goal_time = 0.2 - def setup(self): self.left_object = DataFrame( {'X': np.random.choice(range(0, 10), size=(10000,)), @@ -344,8 +329,6 @@ def time_multiby(self): class Align(object): - goal_time = 0.2 - def setup(self): size = 5 * 10**5 rng = np.arange(0, 10**13, 10**7) @@ -360,3 +343,6 @@ def time_series_align_int64_index(self): def time_series_align_left_monotonic(self): self.ts1.align(self.ts2, join='left') + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/multiindex_object.py b/asv_bench/benchmarks/multiindex_object.py index 0c92214795557..ff202322dbe84 100644 --- a/asv_bench/benchmarks/multiindex_object.py +++ b/asv_bench/benchmarks/multiindex_object.py @@ -4,13 +4,9 @@ import pandas.util.testing as tm from pandas import date_range, MultiIndex -from .pandas_vb_common import setup # noqa - class GetLoc(object): - goal_time = 0.2 - def setup(self): self.mi_large = MultiIndex.from_product( [np.arange(1000), np.arange(20), list(string.ascii_letters)], @@ -46,8 +42,6 @@ def time_small_get_loc_warm(self): class Duplicates(object): - goal_time = 0.2 - def setup(self): size = 65536 arrays = [np.random.randint(0, 8192, size), @@ -62,8 +56,6 @@ def time_remove_unused_levels(self): class Integer(object): - goal_time = 0.2 - def setup(self): self.mi_int = MultiIndex.from_product([np.arange(1000), np.arange(1000)], @@ -82,8 +74,6 @@ def time_is_monotonic(self): class Duplicated(object): - goal_time = 0.2 - def setup(self): n, k = 200, 5000 levels = [np.arange(n), @@ -98,8 +88,6 @@ def time_duplicated(self): class Sortlevel(object): - goal_time = 0.2 - def setup(self): n = 1182720 low, high = -4096, 4096 @@ -124,8 +112,6 @@ def time_sortlevel_one(self): class Values(object): - goal_time = 0.2 - def setup_cache(self): level1 = range(1000) @@ -138,3 +124,6 @@ def time_datetime_level_values_copy(self, mi): def time_datetime_level_values_sliced(self, mi): mi[:10].values + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/offset.py b/asv_bench/benchmarks/offset.py index e161b887ee86f..4570e73cccc71 100644 --- a/asv_bench/benchmarks/offset.py +++ b/asv_bench/benchmarks/offset.py @@ -34,8 +34,6 @@ class ApplyIndex(object): - goal_time = 0.2 - params = other_offsets param_names = ['offset'] @@ -49,8 +47,6 @@ def time_apply_index(self, offset): class OnOffset(object): - goal_time = 0.2 - params = offsets param_names = ['offset'] @@ -67,7 +63,6 @@ def time_on_offset(self, offset): class OffsetSeriesArithmetic(object): - goal_time = 0.2 params = offsets param_names = ['offset'] @@ -83,7 +78,6 @@ def time_add_offset(self, offset): class OffsetDatetimeIndexArithmetic(object): - goal_time = 0.2 params = offsets param_names = ['offset'] @@ -98,7 +92,6 @@ def time_add_offset(self, offset): class OffestDatetimeArithmetic(object): - goal_time = 0.2 params = offsets param_names = ['offset'] diff --git a/asv_bench/benchmarks/pandas_vb_common.py b/asv_bench/benchmarks/pandas_vb_common.py index e255cd94f265b..e7b25d567e03b 100644 --- a/asv_bench/benchmarks/pandas_vb_common.py +++ b/asv_bench/benchmarks/pandas_vb_common.py @@ -2,14 +2,13 @@ from importlib import import_module import numpy as np -from pandas import Panel # Compatibility import for lib for imp in ['pandas._libs.lib', 'pandas.lib']: try: lib = import_module(imp) break - except: + except (ImportError, TypeError, ValueError): pass numeric_dtypes = [np.int64, np.int32, np.uint32, np.uint64, np.float32, @@ -34,7 +33,7 @@ def remove(self, f): """Remove created files""" try: os.remove(f) - except: + except OSError: # On Windows, attempting to remove a file that is in use # causes an exception to be raised pass diff --git a/asv_bench/benchmarks/panel_ctor.py b/asv_bench/benchmarks/panel_ctor.py index ce946c76ed199..47b3ad612f9b1 100644 --- a/asv_bench/benchmarks/panel_ctor.py +++ b/asv_bench/benchmarks/panel_ctor.py @@ -1,14 +1,10 @@ import warnings from datetime import datetime, timedelta -from pandas import DataFrame, DatetimeIndex, date_range - -from .pandas_vb_common import Panel, setup # noqa +from pandas import DataFrame, Panel, DatetimeIndex, date_range class DifferentIndexes(object): - goal_time = 0.2 - def setup(self): self.data_frames = {} start = datetime(1990, 1, 1) @@ -26,8 +22,6 @@ def time_from_dict(self): class SameIndexes(object): - goal_time = 0.2 - def setup(self): idx = DatetimeIndex(start=datetime(1990, 1, 1), end=datetime(2012, 1, 1), @@ -42,8 +36,6 @@ def time_from_dict(self): class TwoIndexes(object): - goal_time = 0.2 - def setup(self): start = datetime(1990, 1, 1) end = datetime(2012, 1, 1) @@ -58,3 +50,6 @@ def setup(self): def time_from_dict(self): with warnings.catch_warnings(record=True): Panel.from_dict(self.data_frames) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/panel_methods.py b/asv_bench/benchmarks/panel_methods.py index a5b1a92e9cf67..a4c12c082236e 100644 --- a/asv_bench/benchmarks/panel_methods.py +++ b/asv_bench/benchmarks/panel_methods.py @@ -1,13 +1,11 @@ import warnings import numpy as np - -from .pandas_vb_common import Panel, setup # noqa +from pandas import Panel class PanelMethods(object): - goal_time = 0.2 params = ['items', 'major', 'minor'] param_names = ['axis'] @@ -22,3 +20,6 @@ def time_pct_change(self, axis): def time_shift(self, axis): with warnings.catch_warnings(record=True): self.panel.shift(1, axis=axis) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/period.py b/asv_bench/benchmarks/period.py index 897a3338c164c..fc34a47fee3e1 100644 --- a/asv_bench/benchmarks/period.py +++ b/asv_bench/benchmarks/period.py @@ -37,8 +37,6 @@ def time_asfreq(self, freq): class PeriodIndexConstructor(object): - goal_time = 0.2 - params = ['D'] param_names = ['freq'] @@ -55,8 +53,6 @@ def time_from_pydatetime(self, freq): class DataFramePeriodColumn(object): - goal_time = 0.2 - def setup(self): self.rng = period_range(start='1/1/1990', freq='S', periods=20000) self.df = DataFrame(index=range(len(self.rng))) @@ -64,10 +60,13 @@ def setup(self): def time_setitem_period_column(self): self.df['col'] = self.rng + def time_set_index(self): + # GH#21582 limited by comparisons of Period objects + self.df['col2'] = self.rng + self.df.set_index('col2', append=True) -class Algorithms(object): - goal_time = 0.2 +class Algorithms(object): params = ['index', 'series'] param_names = ['typ'] @@ -90,8 +89,6 @@ def time_value_counts(self, typ): class Indexing(object): - goal_time = 0.2 - def setup(self): self.index = PeriodIndex(start='1985', periods=1000, freq='D') self.series = Series(range(1000), index=self.index) @@ -114,3 +111,6 @@ def time_align(self): def time_intersection(self): self.index[:750].intersection(self.index[250:]) + + def time_unique(self): + self.index.unique() diff --git a/asv_bench/benchmarks/plotting.py b/asv_bench/benchmarks/plotting.py index 5b49112b0e07d..1373d5f0b4258 100644 --- a/asv_bench/benchmarks/plotting.py +++ b/asv_bench/benchmarks/plotting.py @@ -7,13 +7,9 @@ import matplotlib matplotlib.use('Agg') -from .pandas_vb_common import setup # noqa - class Plotting(object): - goal_time = 0.2 - def setup(self): self.s = Series(np.random.randn(1000000)) self.df = DataFrame({'col': self.s}) @@ -27,8 +23,6 @@ def time_frame_plot(self): class TimeseriesPlotting(object): - goal_time = 0.2 - def setup(self): N = 2000 M = 5 @@ -52,8 +46,6 @@ def time_plot_irregular(self): class Misc(object): - goal_time = 0.6 - def setup(self): N = 500 M = 10 @@ -62,3 +54,6 @@ def setup(self): def time_plot_andrews_curves(self): andrews_curves(self.df, "Name") + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/reindex.py b/asv_bench/benchmarks/reindex.py index 413427a16f40b..82c61a98e2c34 100644 --- a/asv_bench/benchmarks/reindex.py +++ b/asv_bench/benchmarks/reindex.py @@ -2,13 +2,11 @@ import pandas.util.testing as tm from pandas import (DataFrame, Series, DatetimeIndex, MultiIndex, Index, date_range) -from .pandas_vb_common import setup, lib # noqa +from .pandas_vb_common import lib class Reindex(object): - goal_time = 0.2 - def setup(self): rng = DatetimeIndex(start='1/1/1970', periods=10000, freq='1min') self.df = DataFrame(np.random.rand(10000, 10), index=rng, @@ -37,7 +35,6 @@ def time_reindex_multiindex(self): class ReindexMethod(object): - goal_time = 0.2 params = ['pad', 'backfill'] param_names = ['method'] @@ -52,7 +49,6 @@ def time_reindex_method(self, method): class Fillna(object): - goal_time = 0.2 params = ['pad', 'backfill'] param_names = ['method'] @@ -72,8 +68,6 @@ def time_float_32(self, method): class LevelAlign(object): - goal_time = 0.2 - def setup(self): self.index = MultiIndex( levels=[np.arange(10), np.arange(100), np.arange(100)], @@ -94,7 +88,6 @@ def time_reindex_level(self): class DropDuplicates(object): - goal_time = 0.2 params = [True, False] param_names = ['inplace'] @@ -139,8 +132,6 @@ def time_frame_drop_dups_bool(self, inplace): class Align(object): # blog "pandas escaped the zoo" - goal_time = 0.2 - def setup(self): n = 50000 indices = tm.makeStringIndex(n) @@ -156,8 +147,6 @@ def time_align_series_irregular_string(self): class LibFastZip(object): - goal_time = 0.2 - def setup(self): N = 10000 K = 10 @@ -170,3 +159,6 @@ def setup(self): def time_lib_fast_zip(self): lib.fast_zip(self.col_array_list) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/replace.py b/asv_bench/benchmarks/replace.py index 41208125e8f32..d8efaf99e2c4d 100644 --- a/asv_bench/benchmarks/replace.py +++ b/asv_bench/benchmarks/replace.py @@ -1,12 +1,9 @@ import numpy as np import pandas as pd -from .pandas_vb_common import setup # noqa - class FillNa(object): - goal_time = 0.2 params = [True, False] param_names = ['inplace'] @@ -26,7 +23,6 @@ def time_replace(self, inplace): class ReplaceDict(object): - goal_time = 0.2 params = [True, False] param_names = ['inplace'] @@ -42,7 +38,6 @@ def time_replace_series(self, inplace): class Convert(object): - goal_time = 0.5 params = (['DataFrame', 'Series'], ['Timestamp', 'Timedelta']) param_names = ['constructor', 'replace_data'] @@ -56,3 +51,6 @@ def setup(self, constructor, replace_data): def time_replace(self, constructor, replace_data): self.data.replace(self.to_replace) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/reshape.py b/asv_bench/benchmarks/reshape.py index 9044b080c45f9..67fdfb82e72c0 100644 --- a/asv_bench/benchmarks/reshape.py +++ b/asv_bench/benchmarks/reshape.py @@ -1,15 +1,13 @@ +import string from itertools import product import numpy as np from pandas import DataFrame, MultiIndex, date_range, melt, wide_to_long - -from .pandas_vb_common import setup # noqa +import pandas as pd class Melt(object): - goal_time = 0.2 - def setup(self): self.df = DataFrame(np.random.randn(10000, 3), columns=['A', 'B', 'C']) self.df['id1'] = np.random.randint(0, 10, 10000) @@ -21,8 +19,6 @@ def time_melt_dataframe(self): class Pivot(object): - goal_time = 0.2 - def setup(self): N = 10000 index = date_range('1/1/2000', periods=N, freq='h') @@ -37,8 +33,6 @@ def time_reshape_pivot_time_series(self): class SimpleReshape(object): - goal_time = 0.2 - def setup(self): arrays = [np.arange(100).repeat(100), np.roll(np.tile(np.arange(100), 100), 25)] @@ -55,30 +49,38 @@ def time_unstack(self): class Unstack(object): - goal_time = 0.2 + params = ['int', 'category'] - def setup(self): + def setup(self, dtype): m = 100 n = 1000 levels = np.arange(m) index = MultiIndex.from_product([levels] * 2) columns = np.arange(n) - values = np.arange(m * m * n).reshape(m * m, n) + if dtype == 'int': + values = np.arange(m * m * n).reshape(m * m, n) + else: + # the category branch is ~20x slower than int. So we + # cut down the size a bit. Now it's only ~3x slower. + n = 50 + columns = columns[:n] + indices = np.random.randint(0, 52, size=(m * m, n)) + values = np.take(list(string.ascii_letters), indices) + values = [pd.Categorical(v) for v in values.T] + self.df = DataFrame(values, index, columns) self.df2 = self.df.iloc[:-1] - def time_full_product(self): + def time_full_product(self, dtype): self.df.unstack() - def time_without_last_row(self): + def time_without_last_row(self, dtype): self.df2.unstack() class SparseIndex(object): - goal_time = 0.2 - def setup(self): NUM_ROWS = 1000 self.df = DataFrame({'A': np.random.randint(50, size=NUM_ROWS), @@ -95,8 +97,6 @@ def time_unstack(self): class WideToLong(object): - goal_time = 0.2 - def setup(self): nyrs = 20 nidvars = 20 @@ -115,8 +115,6 @@ def time_wide_to_long_big(self): class PivotTable(object): - goal_time = 0.2 - def setup(self): N = 100000 fac1 = np.array(['A', 'B', 'C'], dtype='O') @@ -132,3 +130,20 @@ def setup(self): def time_pivot_table(self): self.df.pivot_table(index='key1', columns=['key2', 'key3']) + + +class GetDummies(object): + def setup(self): + categories = list(string.ascii_letters[:12]) + s = pd.Series(np.random.choice(categories, size=1000000), + dtype=pd.api.types.CategoricalDtype(categories)) + self.s = s + + def time_get_dummies_1d(self): + pd.get_dummies(self.s, sparse=False) + + def time_get_dummies_1d_sparse(self): + pd.get_dummies(self.s, sparse=True) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/rolling.py b/asv_bench/benchmarks/rolling.py index e3bf551fa5f2b..86294e33e1e06 100644 --- a/asv_bench/benchmarks/rolling.py +++ b/asv_bench/benchmarks/rolling.py @@ -1,8 +1,6 @@ import pandas as pd import numpy as np -from .pandas_vb_common import setup # noqa - class Methods(object): @@ -77,3 +75,6 @@ def setup(self, constructor, window, dtype, percentile, interpolation): def time_quantile(self, constructor, window, dtype, percentile, interpolation): self.roll.quantile(percentile, interpolation=interpolation) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/series_methods.py b/asv_bench/benchmarks/series_methods.py index 3f6522c3403d9..46fb5011cc1a5 100644 --- a/asv_bench/benchmarks/series_methods.py +++ b/asv_bench/benchmarks/series_methods.py @@ -4,12 +4,9 @@ import pandas.util.testing as tm from pandas import Series, date_range, NaT -from .pandas_vb_common import setup # noqa - class SeriesConstructor(object): - goal_time = 0.2 params = [None, 'dict'] param_names = ['data'] @@ -26,7 +23,6 @@ def time_constructor(self, data): class IsIn(object): - goal_time = 0.2 params = ['int64', 'object'] param_names = ['dtype'] @@ -38,10 +34,67 @@ def time_isin(self, dtypes): self.s.isin(self.values) +class IsInFloat64(object): + + def setup(self): + self.small = Series([1, 2], dtype=np.float64) + self.many_different_values = np.arange(10**6, dtype=np.float64) + self.few_different_values = np.zeros(10**7, dtype=np.float64) + self.only_nans_values = np.full(10**7, np.nan, dtype=np.float64) + + def time_isin_many_different(self): + # runtime is dominated by creation of the lookup-table + self.small.isin(self.many_different_values) + + def time_isin_few_different(self): + # runtime is dominated by creation of the lookup-table + self.small.isin(self.few_different_values) + + def time_isin_nan_values(self): + # runtime is dominated by creation of the lookup-table + self.small.isin(self.few_different_values) + + +class IsInForObjects(object): + + def setup(self): + self.s_nans = Series(np.full(10**4, np.nan)).astype(np.object) + self.vals_nans = np.full(10**4, np.nan).astype(np.object) + self.s_short = Series(np.arange(2)).astype(np.object) + self.s_long = Series(np.arange(10**5)).astype(np.object) + self.vals_short = np.arange(2).astype(np.object) + self.vals_long = np.arange(10**5).astype(np.object) + # because of nans floats are special: + self.s_long_floats = Series(np.arange(10**5, + dtype=np.float)).astype(np.object) + self.vals_long_floats = np.arange(10**5, + dtype=np.float).astype(np.object) + + def time_isin_nans(self): + # if nan-objects are different objects, + # this has the potential to trigger O(n^2) running time + self.s_nans.isin(self.vals_nans) + + def time_isin_short_series_long_values(self): + # running time dominated by the preprocessing + self.s_short.isin(self.vals_long) + + def time_isin_long_series_short_values(self): + # running time dominated by look-up + self.s_long.isin(self.vals_short) + + def time_isin_long_series_long_values(self): + # no dominating part + self.s_long.isin(self.vals_long) + + def time_isin_long_series_long_values_floats(self): + # no dominating part + self.s_long_floats.isin(self.vals_long_floats) + + class NSort(object): - goal_time = 0.2 - params = ['last', 'first'] + params = ['first', 'last', 'all'] param_names = ['keep'] def setup(self, keep): @@ -56,7 +109,6 @@ def time_nsmallest(self, keep): class Dropna(object): - goal_time = 0.2 params = ['int', 'datetime'] param_names = ['dtype'] @@ -74,7 +126,6 @@ def time_dropna(self, dtype): class Map(object): - goal_time = 0.2 params = ['dict', 'Series'] param_names = 'mapper' @@ -90,8 +141,6 @@ def time_map(self, mapper): class Clip(object): - goal_time = 0.2 - def setup(self): self.s = Series(np.random.randn(50)) @@ -101,7 +150,6 @@ def time_clip(self): class ValueCounts(object): - goal_time = 0.2 params = ['int', 'float', 'object'] param_names = ['dtype'] @@ -114,8 +162,6 @@ def time_value_counts(self, dtype): class Dir(object): - goal_time = 0.2 - def setup(self): self.s = Series(index=tm.makeStringIndex(10000)) @@ -125,8 +171,6 @@ def time_dir_strings(self): class SeriesGetattr(object): # https://github.com/pandas-dev/pandas/issues/19764 - goal_time = 0.2 - def setup(self): self.s = Series(1, index=date_range("2012-01-01", freq='s', @@ -134,3 +178,6 @@ def setup(self): def time_series_datetimeindex_repr(self): getattr(self.s, 'a', None) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/sparse.py b/asv_bench/benchmarks/sparse.py index dcb7694abc2ad..64f87c1670170 100644 --- a/asv_bench/benchmarks/sparse.py +++ b/asv_bench/benchmarks/sparse.py @@ -5,8 +5,6 @@ from pandas import (SparseSeries, SparseDataFrame, SparseArray, Series, date_range, MultiIndex) -from .pandas_vb_common import setup # noqa - def make_array(size, dense_proportion, fill_value, dtype): dense_size = int(size * dense_proportion) @@ -18,8 +16,6 @@ def make_array(size, dense_proportion, fill_value, dtype): class SparseSeriesToFrame(object): - goal_time = 0.2 - def setup(self): K = 50 N = 50001 @@ -37,7 +33,6 @@ def time_series_to_frame(self): class SparseArrayConstructor(object): - goal_time = 0.2 params = ([0.1, 0.01], [0, np.nan], [np.int64, np.float64, np.object]) param_names = ['dense_proportion', 'fill_value', 'dtype'] @@ -52,8 +47,6 @@ def time_sparse_array(self, dense_proportion, fill_value, dtype): class SparseDataFrameConstructor(object): - goal_time = 0.2 - def setup(self): N = 1000 self.arr = np.arange(N) @@ -72,8 +65,6 @@ def time_from_dict(self): class FromCoo(object): - goal_time = 0.2 - def setup(self): self.matrix = scipy.sparse.coo_matrix(([3.0, 1.0, 2.0], ([1, 0, 0], [0, 2, 3])), @@ -85,8 +76,6 @@ def time_sparse_series_from_coo(self): class ToCoo(object): - goal_time = 0.2 - def setup(self): s = Series([np.nan] * 10000) s[0] = 3.0 @@ -103,7 +92,6 @@ def time_sparse_series_to_coo(self): class Arithmetic(object): - goal_time = 0.2 params = ([0.1, 0.01], [0, np.nan]) param_names = ['dense_proportion', 'fill_value'] @@ -129,7 +117,6 @@ def time_divide(self, dense_proportion, fill_value): class ArithmeticBlock(object): - goal_time = 0.2 params = [np.nan, 0] param_names = ['fill_value'] @@ -160,3 +147,6 @@ def time_addition(self, fill_value): def time_division(self, fill_value): self.arr1 / self.arr2 + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/stat_ops.py b/asv_bench/benchmarks/stat_ops.py index c447c78d0d070..5c777c00261e1 100644 --- a/asv_bench/benchmarks/stat_ops.py +++ b/asv_bench/benchmarks/stat_ops.py @@ -1,8 +1,6 @@ import numpy as np import pandas as pd -from .pandas_vb_common import setup # noqa - ops = ['mean', 'sum', 'median', 'std', 'skew', 'kurt', 'mad', 'prod', 'sem', 'var'] @@ -10,7 +8,6 @@ class FrameOps(object): - goal_time = 0.2 params = [ops, ['float', 'int'], [0, 1], [True, False]] param_names = ['op', 'dtype', 'axis', 'use_bottleneck'] @@ -18,7 +15,7 @@ def setup(self, op, dtype, axis, use_bottleneck): df = pd.DataFrame(np.random.randn(100000, 4)).astype(dtype) try: pd.options.compute.use_bottleneck = use_bottleneck - except: + except TypeError: from pandas.core import nanops nanops._USE_BOTTLENECK = use_bottleneck self.df_func = getattr(df, op) @@ -29,7 +26,6 @@ def time_op(self, op, dtype, axis, use_bottleneck): class FrameMultiIndexOps(object): - goal_time = 0.2 params = ([0, 1, [0, 1]], ops) param_names = ['level', 'op'] @@ -48,7 +44,6 @@ def time_op(self, level, op): class SeriesOps(object): - goal_time = 0.2 params = [ops, ['float', 'int'], [True, False]] param_names = ['op', 'dtype', 'use_bottleneck'] @@ -56,7 +51,7 @@ def setup(self, op, dtype, use_bottleneck): s = pd.Series(np.random.randn(100000)).astype(dtype) try: pd.options.compute.use_bottleneck = use_bottleneck - except: + except TypeError: from pandas.core import nanops nanops._USE_BOTTLENECK = use_bottleneck self.s_func = getattr(s, op) @@ -67,7 +62,6 @@ def time_op(self, op, dtype, use_bottleneck): class SeriesMultiIndexOps(object): - goal_time = 0.2 params = ([0, 1, [0, 1]], ops) param_names = ['level', 'op'] @@ -86,7 +80,6 @@ def time_op(self, level, op): class Rank(object): - goal_time = 0.2 params = [['DataFrame', 'Series'], [True, False]] param_names = ['constructor', 'pct'] @@ -103,7 +96,6 @@ def time_average_old(self, constructor, pct): class Correlation(object): - goal_time = 0.2 params = ['spearman', 'kendall', 'pearson'] param_names = ['method'] @@ -112,3 +104,6 @@ def setup(self, method): def time_corr(self, method): self.df.corr(method=method) + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/strings.py b/asv_bench/benchmarks/strings.py index b203c8b0fa5c9..d880fb258560d 100644 --- a/asv_bench/benchmarks/strings.py +++ b/asv_bench/benchmarks/strings.py @@ -1,20 +1,15 @@ import warnings import numpy as np -from pandas import Series +from pandas import Series, DataFrame import pandas.util.testing as tm class Methods(object): - goal_time = 0.2 - def setup(self): self.s = Series(tm.makeStringIndex(10**5)) - def time_cat(self): - self.s.str.cat(sep=',') - def time_center(self): self.s.str.center(100) @@ -73,7 +68,6 @@ def time_lower(self): class Repeat(object): - goal_time = 0.2 params = ['int', 'array'] param_names = ['repeats'] @@ -87,9 +81,33 @@ def time_repeat(self, repeats): self.s.str.repeat(self.repeat) +class Cat(object): + + params = ([0, 3], [None, ','], [None, '-'], [0.0, 0.001, 0.15]) + param_names = ['other_cols', 'sep', 'na_rep', 'na_frac'] + + def setup(self, other_cols, sep, na_rep, na_frac): + N = 10 ** 5 + mask_gen = lambda: np.random.choice([True, False], N, + p=[1 - na_frac, na_frac]) + self.s = Series(tm.makeStringIndex(N)).where(mask_gen()) + if other_cols == 0: + # str.cat self-concatenates only for others=None + self.others = None + else: + self.others = DataFrame({i: tm.makeStringIndex(N).where(mask_gen()) + for i in range(other_cols)}) + + def time_cat(self, other_cols, sep, na_rep, na_frac): + # before the concatenation (one caller + other_cols columns), the total + # expected fraction of rows containing any NaN is: + # reduce(lambda t, _: t + (1 - t) * na_frac, range(other_cols + 1), 0) + # for other_cols=3 and na_frac=0.15, this works out to ~48% + self.s.str.cat(others=self.others, sep=sep, na_rep=na_rep) + + class Contains(object): - goal_time = 0.2 params = [True, False] param_names = ['regex'] @@ -102,7 +120,6 @@ def time_contains(self, regex): class Split(object): - goal_time = 0.2 params = [True, False] param_names = ['expand'] @@ -115,8 +132,6 @@ def time_split(self, expand): class Dummies(object): - goal_time = 0.2 - def setup(self): self.s = Series(tm.makeStringIndex(10**5)).str.join('|') @@ -126,8 +141,6 @@ def time_get_dummies(self): class Encode(object): - goal_time = 0.2 - def setup(self): self.ser = Series(tm.makeUnicodeIndex()) @@ -137,8 +150,6 @@ def time_encode_decode(self): class Slice(object): - goal_time = 0.2 - def setup(self): self.s = Series(['abcdefg', np.nan] * 500000) diff --git a/asv_bench/benchmarks/timedelta.py b/asv_bench/benchmarks/timedelta.py index 3fe75b3c34299..01d53fb9cbbd9 100644 --- a/asv_bench/benchmarks/timedelta.py +++ b/asv_bench/benchmarks/timedelta.py @@ -6,8 +6,6 @@ class TimedeltaConstructor(object): - goal_time = 0.2 - def time_from_int(self): Timedelta(123456789) @@ -36,8 +34,6 @@ def time_from_missing(self): class ToTimedelta(object): - goal_time = 0.2 - def setup(self): self.ints = np.random.randint(0, 60, size=10000) self.str_days = [] @@ -58,7 +54,6 @@ def time_convert_string_seconds(self): class ToTimedeltaErrors(object): - goal_time = 0.2 params = ['coerce', 'ignore'] param_names = ['errors'] @@ -73,8 +68,6 @@ def time_convert(self, errors): class TimedeltaOps(object): - goal_time = 0.2 - def setup(self): self.td = to_timedelta(np.arange(1000000)) self.ts = Timestamp('2000') @@ -85,8 +78,6 @@ def time_add_td_ts(self): class TimedeltaProperties(object): - goal_time = 0.2 - def setup_cache(self): td = Timedelta(days=365, minutes=35, seconds=25, milliseconds=35) return td @@ -106,8 +97,6 @@ def time_timedelta_nanoseconds(self, td): class DatetimeAccessor(object): - goal_time = 0.2 - def setup_cache(self): N = 100000 series = Series(timedelta_range('1 days', periods=N, freq='h')) diff --git a/asv_bench/benchmarks/timeseries.py b/asv_bench/benchmarks/timeseries.py index eada401d2930b..84bdcc5fa30f2 100644 --- a/asv_bench/benchmarks/timeseries.py +++ b/asv_bench/benchmarks/timeseries.py @@ -1,4 +1,3 @@ -import warnings from datetime import timedelta import numpy as np @@ -9,12 +8,9 @@ except ImportError: from pandas.tseries.converter import DatetimeConverter -from .pandas_vb_common import setup # noqa - class DatetimeIndex(object): - goal_time = 0.2 params = ['dst', 'repeated', 'tz_aware', 'tz_naive'] param_names = ['index_type'] @@ -61,8 +57,6 @@ def time_to_pydatetime(self, index_type): class TzLocalize(object): - goal_time = 0.2 - def setup(self): dst_rng = date_range(start='10/29/2000 1:00:00', end='10/29/2000 1:59:59', freq='S') @@ -80,7 +74,6 @@ def time_infer_dst(self): class ResetIndex(object): - goal_time = 0.2 params = [None, 'US/Eastern'] param_names = 'tz' @@ -94,7 +87,6 @@ def time_reest_datetimeindex(self, tz): class Factorize(object): - goal_time = 0.2 params = [None, 'Asia/Tokyo'] param_names = 'tz' @@ -109,7 +101,6 @@ def time_factorize(self, tz): class InferFreq(object): - goal_time = 0.2 params = [None, 'D', 'B'] param_names = ['freq'] @@ -126,8 +117,6 @@ def time_infer_freq(self, freq): class TimeDatetimeConverter(object): - goal_time = 0.2 - def setup(self): N = 100000 self.rng = date_range(start='1/1/2000', periods=N, freq='T') @@ -138,7 +127,6 @@ def time_convert(self): class Iteration(object): - goal_time = 0.2 params = [date_range, period_range] param_names = ['time_index'] @@ -159,7 +147,6 @@ def time_iter_preexit(self, time_index): class ResampleDataFrame(object): - goal_time = 0.2 params = ['max', 'mean', 'min'] param_names = ['method'] @@ -174,7 +161,6 @@ def time_method(self, method): class ResampleSeries(object): - goal_time = 0.2 params = (['period', 'datetime'], ['5min', '1D'], ['mean', 'ohlc']) param_names = ['index', 'freq', 'method'] @@ -195,8 +181,6 @@ def time_resample(self, index, freq, method): class ResampleDatetetime64(object): # GH 7754 - goal_time = 0.2 - def setup(self): rng3 = date_range(start='2000-01-01 00:00:00', end='2000-01-01 10:00:00', freq='555000U') @@ -208,7 +192,6 @@ def time_resample(self): class AsOf(object): - goal_time = 0.2 params = ['DataFrame', 'Series'] param_names = ['constructor'] @@ -256,7 +239,6 @@ def time_asof_nan_single(self, constructor): class SortIndex(object): - goal_time = 0.2 params = [True, False] param_names = ['monotonic'] @@ -276,8 +258,6 @@ def time_get_slice(self, monotonic): class IrregularOps(object): - goal_time = 0.2 - def setup(self): N = 10**5 idx = date_range(start='1/1/2000', periods=N, freq='s') @@ -291,8 +271,6 @@ def time_add(self): class Lookup(object): - goal_time = 0.2 - def setup(self): N = 1500000 rng = date_range(start='1/1/2000', periods=N, freq='S') @@ -306,8 +284,6 @@ def time_lookup_and_cleanup(self): class ToDatetimeYYYYMMDD(object): - goal_time = 0.2 - def setup(self): rng = date_range(start='1/1/2000', periods=10000, freq='D') self.stringsD = Series(rng.strftime('%Y%m%d')) @@ -318,8 +294,6 @@ def time_format_YYYYMMDD(self): class ToDatetimeISO8601(object): - goal_time = 0.2 - def setup(self): rng = date_range(start='1/1/2000', periods=20000, freq='H') self.strings = rng.strftime('%Y-%m-%d %H:%M:%S').tolist() @@ -343,9 +317,24 @@ def time_iso8601_tz_spaceformat(self): to_datetime(self.strings_tz_space) -class ToDatetimeFormat(object): +class ToDatetimeNONISO8601(object): + + def setup(self): + N = 10000 + half = int(N / 2) + ts_string_1 = 'March 1, 2018 12:00:00+0400' + ts_string_2 = 'March 1, 2018 12:00:00+0500' + self.same_offset = [ts_string_1] * N + self.diff_offset = [ts_string_1] * half + [ts_string_2] * half + + def time_same_offset(self): + to_datetime(self.same_offset) + + def time_different_offset(self): + to_datetime(self.diff_offset) - goal_time = 0.2 + +class ToDatetimeFormat(object): def setup(self): self.s = Series(['19MAY11', '19MAY11:00:00:00'] * 100000) @@ -360,7 +349,6 @@ def time_no_exact(self): class ToDatetimeCache(object): - goal_time = 0.2 params = [True, False] param_names = ['cache'] @@ -398,3 +386,6 @@ def time_dt_accessor(self): def time_dt_accessor_normalize(self): self.series.dt.normalize() + + +from .pandas_vb_common import setup # noqa: F401 diff --git a/asv_bench/benchmarks/timestamp.py b/asv_bench/benchmarks/timestamp.py index c142a9b59fc43..8eaf815eaa962 100644 --- a/asv_bench/benchmarks/timestamp.py +++ b/asv_bench/benchmarks/timestamp.py @@ -29,8 +29,6 @@ def time_fromtimestamp(self): class TimestampProperties(object): - goal_time = 0.2 - _tzs = [None, pytz.timezone('Europe/Amsterdam')] _freqs = [None, 'B'] params = [_tzs, _freqs] @@ -89,8 +87,6 @@ def time_microsecond(self, tz, freq): class TimestampOps(object): - goal_time = 0.2 - params = [None, 'US/Eastern'] param_names = ['tz'] @@ -108,8 +104,6 @@ def time_to_pydatetime(self, tz): class TimestampAcrossDst(object): - goal_time = 0.2 - def setup(self): dt = datetime.datetime(2016, 3, 27, 1) self.tzinfo = pytz.timezone('CET').localize(dt, is_dst=False).tzinfo diff --git a/asv_bench/vbench_to_asv.py b/asv_bench/vbench_to_asv.py deleted file mode 100644 index b1179387e65d5..0000000000000 --- a/asv_bench/vbench_to_asv.py +++ /dev/null @@ -1,163 +0,0 @@ -import ast -import vbench -import os -import sys -import astor -import glob - - -def vbench_to_asv_source(bench, kinds=None): - tab = ' ' * 4 - if kinds is None: - kinds = ['time'] - - output = 'class {}(object):\n'.format(bench.name) - output += tab + 'goal_time = 0.2\n\n' - - if bench.setup: - indented_setup = [tab * 2 + '{}\n'.format(x) for x in bench.setup.splitlines()] - output += tab + 'def setup(self):\n' + ''.join(indented_setup) + '\n' - - for kind in kinds: - output += tab + 'def {}_{}(self):\n'.format(kind, bench.name) - for line in bench.code.splitlines(): - output += tab * 2 + line + '\n' - output += '\n\n' - - if bench.cleanup: - output += tab + 'def teardown(self):\n' + tab * 2 + bench.cleanup - - output += '\n\n' - return output - - -class AssignToSelf(ast.NodeTransformer): - def __init__(self): - super(AssignToSelf, self).__init__() - self.transforms = {} - self.imports = [] - - self.in_class_define = False - self.in_setup = False - - def visit_ClassDef(self, node): - self.transforms = {} - self.in_class_define = True - - functions_to_promote = [] - setup_func = None - - for class_func in ast.iter_child_nodes(node): - if isinstance(class_func, ast.FunctionDef): - if class_func.name == 'setup': - setup_func = class_func - for anon_func in ast.iter_child_nodes(class_func): - if isinstance(anon_func, ast.FunctionDef): - functions_to_promote.append(anon_func) - - if setup_func: - for func in functions_to_promote: - setup_func.body.remove(func) - func.args.args.insert(0, ast.Name(id='self', ctx=ast.Load())) - node.body.append(func) - self.transforms[func.name] = 'self.' + func.name - - ast.fix_missing_locations(node) - - self.generic_visit(node) - - return node - - def visit_TryExcept(self, node): - if any(isinstance(x, (ast.Import, ast.ImportFrom)) for x in node.body): - self.imports.append(node) - else: - self.generic_visit(node) - return node - - def visit_Assign(self, node): - for target in node.targets: - if isinstance(target, ast.Name) and not isinstance(target.ctx, ast.Param) and not self.in_class_define: - self.transforms[target.id] = 'self.' + target.id - self.generic_visit(node) - - return node - - def visit_Name(self, node): - new_node = node - if node.id in self.transforms: - if not isinstance(node.ctx, ast.Param): - new_node = ast.Attribute(value=ast.Name(id='self', ctx=node.ctx), attr=node.id, ctx=node.ctx) - - self.generic_visit(node) - - return ast.copy_location(new_node, node) - - def visit_Import(self, node): - self.imports.append(node) - - def visit_ImportFrom(self, node): - self.imports.append(node) - - def visit_FunctionDef(self, node): - """Delete functions that are empty due to imports being moved""" - self.in_class_define = False - - self.generic_visit(node) - - if node.body: - return node - - -def translate_module(target_module): - g_vars = {} - l_vars = {} - exec('import ' + target_module) in g_vars - - print(target_module) - module = eval(target_module, g_vars) - - benchmarks = [] - for obj_str in dir(module): - obj = getattr(module, obj_str) - if isinstance(obj, vbench.benchmark.Benchmark): - benchmarks.append(obj) - - if not benchmarks: - return - - rewritten_output = '' - for bench in benchmarks: - rewritten_output += vbench_to_asv_source(bench) - - with open('rewrite.py', 'w') as f: - f.write(rewritten_output) - - ast_module = ast.parse(rewritten_output) - - transformer = AssignToSelf() - transformed_module = transformer.visit(ast_module) - - unique_imports = {astor.to_source(node): node for node in transformer.imports} - - transformed_module.body = unique_imports.values() + transformed_module.body - - transformed_source = astor.to_source(transformed_module) - - with open('benchmarks/{}.py'.format(target_module), 'w') as f: - f.write(transformed_source) - - -if __name__ == '__main__': - cwd = os.getcwd() - new_dir = os.path.join(os.path.dirname(__file__), '../vb_suite') - sys.path.insert(0, new_dir) - - for module in glob.glob(os.path.join(new_dir, '*.py')): - mod = os.path.basename(module) - if mod in ['make.py', 'measure_memory_consumption.py', 'perf_HEAD.py', 'run_suite.py', 'test_perf.py', 'generate_rst_files.py', 'test.py', 'suite.py']: - continue - print('') - print(mod) - - translate_module(mod.replace('.py', '')) diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000000000..373c22fdf8e62 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,25 @@ +# Adapted from https://github.com/numba/numba/blob/master/azure-pipelines.yml +jobs: +# Mac and Linux could potentially use the same template +# except it isn't clear how to use a different build matrix +# for each, so for now they are separate +- template: ci/azure/macos.yml + parameters: + name: macOS + vmImage: xcode9-macos10.13 +- template: ci/azure/linux.yml + parameters: + name: Linux + vmImage: ubuntu-16.04 + +# Windows Python 2.7 needs VC 9.0 installed, and not sure +# how to make that a conditional task, so for now these are +# separate templates as well +- template: ci/azure/windows.yml + parameters: + name: Windows + vmImage: vs2017-win2016 +- template: ci/azure/windows-py27.yml + parameters: + name: WindowsPy27 + vmImage: vs2017-win2016 diff --git a/ci/azure/linux.yml b/ci/azure/linux.yml new file mode 100644 index 0000000000000..b5a8e36d5097d --- /dev/null +++ b/ci/azure/linux.yml @@ -0,0 +1,81 @@ +parameters: + name: '' + vmImage: '' + +jobs: +- job: ${{ parameters.name }} + pool: + vmImage: ${{ parameters.vmImage }} + strategy: + maxParallel: 11 + matrix: + py27_np_19: + ENV_FILE: ci/deps/azure-27-compat.yaml + CONDA_PY: "27" + CONDA_ENV: pandas + TEST_ARGS: "--skip-slow --skip-network" + + py36_locale: + ENV_FILE: ci/deps/azure-37-locale.yaml + CONDA_PY: "37" + CONDA_ENV: pandas + TEST_ARGS: "--skip-slow --skip-network" + LOCALE_OVERRIDE: "zh_CN.UTF-8" + + py36_locale_slow: + ENV_FILE: ci/deps/azure-36-locale_slow.yaml + CONDA_PY: "36" + CONDA_ENV: pandas + TEST_ARGS: "--only-slow --skip-network" + + steps: + - script: | + if [ "$(uname)" == "Linux" ]; then sudo apt-get install -y libc6-dev-i386; fi + echo "Installing Miniconda"{ + ci/incremental/install_miniconda.sh + export PATH=$HOME/miniconda3/bin:$PATH + echo "Setting up Conda environment" + ci/incremental/setup_conda_environment.sh + displayName: 'Before Install' + - script: | + export PATH=$HOME/miniconda3/bin:$PATH + ci/incremental/build.sh + displayName: 'Build' + - script: | + export PATH=$HOME/miniconda3/bin:$PATH + ci/script_single.sh + ci/script_multi.sh + echo "[Test done]" + displayName: 'Test' + - script: | + export PATH=$HOME/miniconda3/bin:$PATH + source activate pandas && pushd /tmp && python -c "import pandas; pandas.show_versions();" && popd + - task: PublishTestResults@2 + inputs: + testResultsFiles: 'test-data-*.xml' + testRunTitle: 'Linux' + - powershell: | + $junitXml = "test-data-single.xml" + $(Get-Content $junitXml | Out-String) -match 'failures="(.*?)"' + if ($matches[1] -eq 0) + { + Write-Host "No test failures in test-data-single" + } + else + { + # note that this will produce $LASTEXITCODE=1 + Write-Error "$($matches[1]) tests failed" + } + + $junitXmlMulti = "test-data-multiple.xml" + $(Get-Content $junitXmlMulti | Out-String) -match 'failures="(.*?)"' + if ($matches[1] -eq 0) + { + Write-Host "No test failures in test-data-multi" + } + else + { + # note that this will produce $LASTEXITCODE=1 + Write-Error "$($matches[1]) tests failed" + } + displayName: Check for test failures diff --git a/ci/azure/macos.yml b/ci/azure/macos.yml new file mode 100644 index 0000000000000..16f2fa2d4890f --- /dev/null +++ b/ci/azure/macos.yml @@ -0,0 +1,68 @@ +parameters: + name: '' + vmImage: '' + +jobs: +- job: ${{ parameters.name }} + pool: + vmImage: ${{ parameters.vmImage }} + strategy: + maxParallel: 11 + matrix: + py35_np_120: + ENV_FILE: ci/deps/azure-macos-35.yaml + CONDA_PY: "35" + CONDA_ENV: pandas + TEST_ARGS: "--skip-slow --skip-network" + + steps: + - script: | + if [ "$(uname)" == "Linux" ]; then sudo apt-get install -y libc6-dev-i386; fi + echo "Installing Miniconda" + ci/incremental/install_miniconda.sh + export PATH=$HOME/miniconda3/bin:$PATH + echo "Setting up Conda environment" + ci/incremental/setup_conda_environment.sh + displayName: 'Before Install' + - script: | + export PATH=$HOME/miniconda3/bin:$PATH + ci/incremental/build.sh + displayName: 'Build' + - script: | + export PATH=$HOME/miniconda3/bin:$PATH + ci/script_single.sh + ci/script_multi.sh + echo "[Test done]" + displayName: 'Test' + - script: | + export PATH=$HOME/miniconda3/bin:$PATH + source activate pandas && pushd /tmp && python -c "import pandas; pandas.show_versions();" && popd + - task: PublishTestResults@2 + inputs: + testResultsFiles: 'test-data-*.xml' + testRunTitle: 'MacOS-35' + - powershell: | + $junitXml = "test-data-single.xml" + $(Get-Content $junitXml | Out-String) -match 'failures="(.*?)"' + if ($matches[1] -eq 0) + { + Write-Host "No test failures in test-data-single" + } + else + { + # note that this will produce $LASTEXITCODE=1 + Write-Error "$($matches[1]) tests failed" + } + + $junitXmlMulti = "test-data-multiple.xml" + $(Get-Content $junitXmlMulti | Out-String) -match 'failures="(.*?)"' + if ($matches[1] -eq 0) + { + Write-Host "No test failures in test-data-multi" + } + else + { + # note that this will produce $LASTEXITCODE=1 + Write-Error "$($matches[1]) tests failed" + } + displayName: Check for test failures \ No newline at end of file diff --git a/ci/azure/windows-py27.yml b/ci/azure/windows-py27.yml new file mode 100644 index 0000000000000..fd72b7080e84d --- /dev/null +++ b/ci/azure/windows-py27.yml @@ -0,0 +1,58 @@ +parameters: + name: '' + vmImage: '' + +jobs: +- job: ${{ parameters.name }} + pool: + vmImage: ${{ parameters.vmImage }} + strategy: + maxParallel: 11 + matrix: + py36_np121: + ENV_FILE: ci/deps/azure-windows-27.yaml + CONDA_PY: "27" + CONDA_ENV: pandas + + steps: + - task: CondaEnvironment@1 + inputs: + updateConda: no + packageSpecs: '' + + # Need to install VC 9.0 only for Python 2.7 + # Once we understand how to do tasks conditional on build matrix variables + # we could merge this into azure-windows.yml + - powershell: | + $wc = New-Object net.webclient + $wc.Downloadfile("https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi", "VCForPython27.msi") + Start-Process "VCForPython27.msi" /qn -Wait + displayName: 'Install VC 9.0' + + - script: | + ci\\incremental\\setup_conda_environment.cmd + displayName: 'Before Install' + - script: | + ci\\incremental\\build.cmd + displayName: 'Build' + - script: | + call activate %CONDA_ENV% + pytest --junitxml=test-data.xml --skip-slow --skip-network pandas -n 2 -r sxX --strict --durations=10 %* + displayName: 'Test' + - task: PublishTestResults@2 + inputs: + testResultsFiles: 'test-data.xml' + testRunTitle: 'Windows 27' + - powershell: | + $junitXml = "test-data.xml" + $(Get-Content $junitXml | Out-String) -match 'failures="(.*?)"' + if ($matches[1] -eq 0) + { + Write-Host "No test failures in test-data" + } + else + { + # note that this will produce $LASTEXITCODE=1 + Write-Error "$($matches[1]) tests failed" + } + displayName: Check for test failures diff --git a/ci/azure/windows.yml b/ci/azure/windows.yml new file mode 100644 index 0000000000000..9b87ac7711f40 --- /dev/null +++ b/ci/azure/windows.yml @@ -0,0 +1,49 @@ +parameters: + name: '' + vmImage: '' + +jobs: +- job: ${{ parameters.name }} + pool: + vmImage: ${{ parameters.vmImage }} + strategy: + maxParallel: 11 + matrix: + py36_np14: + ENV_FILE: ci/deps/azure-windows-36.yaml + CONDA_PY: "36" + CONDA_ENV: pandas + + steps: + - task: CondaEnvironment@1 + inputs: + updateConda: no + packageSpecs: '' + + - script: | + ci\\incremental\\setup_conda_environment.cmd + displayName: 'Before Install' + - script: | + ci\\incremental\\build.cmd + displayName: 'Build' + - script: | + call activate %CONDA_ENV% + pytest --junitxml=test-data.xml --skip-slow --skip-network pandas -n 2 -r sxX --strict --durations=10 %* + displayName: 'Test' + - task: PublishTestResults@2 + inputs: + testResultsFiles: 'test-data.xml' + testRunTitle: 'Windows 36' + - powershell: | + $junitXml = "test-data.xml" + $(Get-Content $junitXml | Out-String) -match 'failures="(.*?)"' + if ($matches[1] -eq 0) + { + Write-Host "No test failures in test-data" + } + else + { + # note that this will produce $LASTEXITCODE=1 + Write-Error "$($matches[1]) tests failed" + } + displayName: Check for test failures \ No newline at end of file diff --git a/ci/build_docs.sh b/ci/build_docs.sh index 90a666dc34ed7..f445447e3565c 100755 --- a/ci/build_docs.sh +++ b/ci/build_docs.sh @@ -8,15 +8,6 @@ fi cd "$TRAVIS_BUILD_DIR" echo "inside $0" -git show --pretty="format:" --name-only HEAD~5.. --first-parent | grep -P "rst|txt|doc" - -# if [ "$?" != "0" ]; then -# echo "Skipping doc build, none were modified" -# # nope, skip docs build -# exit 0 -# fi - - if [ "$DOC" ]; then echo "Will build docs" @@ -60,15 +51,6 @@ if [ "$DOC" ]; then git remote -v git push origin gh-pages -f - - echo "Running doctests" - cd "$TRAVIS_BUILD_DIR" - pytest --doctest-modules \ - pandas/core/reshape/concat.py \ - pandas/core/reshape/pivot.py \ - pandas/core/reshape/reshape.py \ - pandas/core/reshape/tile.py - fi exit 0 diff --git a/ci/check_imports.py b/ci/check_imports.py deleted file mode 100644 index d6f24ebcc4d3e..0000000000000 --- a/ci/check_imports.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Check that certain modules are not loaded by `import pandas` -""" -import sys - -blacklist = { - 'bs4', - 'html5lib', - 'ipython', - 'jinja2' - 'lxml', - 'numexpr', - 'openpyxl', - 'py', - 'pytest', - 's3fs', - 'scipy', - 'tables', - 'xlrd', - 'xlsxwriter', - 'xlwt', -} - - -def main(): - import pandas # noqa - - modules = set(x.split('.')[0] for x in sys.modules) - imported = modules & blacklist - if modules & blacklist: - sys.exit("Imported {}".format(imported)) - - -if __name__ == '__main__': - main() diff --git a/ci/install_circle.sh b/ci/circle/install_circle.sh similarity index 80% rename from ci/install_circle.sh rename to ci/circle/install_circle.sh index 5ffff84c88488..f8bcf6bcffc99 100755 --- a/ci/install_circle.sh +++ b/ci/circle/install_circle.sh @@ -6,14 +6,7 @@ echo "[home_dir: $home_dir]" echo "[ls -ltr]" ls -ltr -echo "[Using clean Miniconda install]" -rm -rf "$MINICONDA_DIR" - -# install miniconda -wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -q -O miniconda.sh || exit 1 -bash miniconda.sh -b -p "$MINICONDA_DIR" || exit 1 - -export PATH="$MINICONDA_DIR/bin:$PATH" +apt-get update -y && apt-get install -y build-essential postgresql-client-9.6 echo "[update conda]" conda config --set ssl_verify false || exit 1 @@ -48,9 +41,17 @@ source $ENVS_FILE # edit the locale override if needed if [ -n "$LOCALE_OVERRIDE" ]; then + + apt-get update && apt-get -y install locales locales-all + + export LANG=$LOCALE_OVERRIDE + export LC_ALL=$LOCALE_OVERRIDE + + python -c "import locale; locale.setlocale(locale.LC_ALL, \"$LOCALE_OVERRIDE\")" || exit 1; + echo "[Adding locale to the first line of pandas/__init__.py]" rm -f pandas/__init__.pyc - sedc="3iimport locale\nlocale.setlocale(locale.LC_ALL, '$LOCALE_OVERRIDE')\n" + sedc="3iimport locale\nlocale.setlocale(locale.LC_ALL, \"$LOCALE_OVERRIDE\")\n" sed -i "$sedc" pandas/__init__.py echo "[head -4 pandas/__init__.py]" head -4 pandas/__init__.py diff --git a/ci/circle/run_circle.sh b/ci/circle/run_circle.sh new file mode 100755 index 0000000000000..803724c2f492d --- /dev/null +++ b/ci/circle/run_circle.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +echo "[running tests]" +export PATH="$MINICONDA_DIR/bin:$PATH" + +source activate pandas + +echo "pytest --strict --durations=10 --color=no --junitxml=$CIRCLE_TEST_REPORTS/reports/junit.xml $@ pandas" +pytest --strict --durations=10 --color=no --junitxml=$CIRCLE_TEST_REPORTS/reports/junit.xml $@ pandas diff --git a/ci/show_circle.sh b/ci/circle/show_circle.sh similarity index 100% rename from ci/show_circle.sh rename to ci/circle/show_circle.sh diff --git a/ci/code_checks.sh b/ci/code_checks.sh new file mode 100755 index 0000000000000..330901ba56fbd --- /dev/null +++ b/ci/code_checks.sh @@ -0,0 +1,165 @@ +#!/bin/bash +# +# Run checks related to code quality. +# +# This script is intended for both the CI and to check locally that code standards are +# respected. We are currently linting (PEP-8 and similar), looking for patterns of +# common mistakes (sphinx directives with missing blank lines, old style classes, +# unwanted imports...), and we also run doctests here (currently some files only). +# In the future we may want to add the validation of docstrings and other checks here. +# +# Usage: +# $ ./ci/code_checks.sh # run all checks +# $ ./ci/code_checks.sh lint # run linting only +# $ ./ci/code_checks.sh patterns # check for patterns that should not exist +# $ ./ci/code_checks.sh doctests # run doctests + +echo "inside $0" +[[ $LINT ]] || { echo "NOT Linting. To lint use: LINT=true $0 $1"; exit 0; } +[[ -z "$1" || "$1" == "lint" || "$1" == "patterns" || "$1" == "doctests" ]] || { echo "Unknown command $1. Usage: $0 [lint|patterns|doctests]"; exit 9999; } + +source activate pandas +RET=0 +CHECK=$1 + + +### LINTING ### +if [[ -z "$CHECK" || "$CHECK" == "lint" ]]; then + + # `setup.cfg` contains the list of error codes that are being ignored in flake8 + + echo "flake8 --version" + flake8 --version + + # pandas/_libs/src is C code, so no need to search there. + MSG='Linting .py code' ; echo $MSG + flake8 . + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Linting .pyx code' ; echo $MSG + flake8 pandas --filename=*.pyx --select=E501,E302,E203,E111,E114,E221,E303,E128,E231,E126,E265,E305,E301,E127,E261,E271,E129,W291,E222,E241,E123,F403,C400,C401,C402,C403,C404,C405,C406,C407,C408,C409,C410,C411 + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Linting .pxd and .pxi.in' ; echo $MSG + flake8 pandas/_libs --filename=*.pxi.in,*.pxd --select=E501,E302,E203,E111,E114,E221,E303,E231,E126,F403 + RET=$(($RET + $?)) ; echo $MSG "DONE" + + # Check that cython casting is of the form `obj` as opposed to ` obj`; + # it doesn't make a difference, but we want to be internally consistent. + # Note: this grep pattern is (intended to be) equivalent to the python + # regex r'(?])> ' + MSG='Linting .pyx code for spacing conventions in casting' ; echo $MSG + ! grep -r -E --include '*.pyx' --include '*.pxi.in' '[a-zA-Z0-9*]> ' pandas/_libs + RET=$(($RET + $?)) ; echo $MSG "DONE" + + # readability/casting: Warnings about C casting instead of C++ casting + # runtime/int: Warnings about using C number types instead of C++ ones + # build/include_subdir: Warnings about prefacing included header files with directory + + # We don't lint all C files because we don't want to lint any that are built + # from Cython files nor do we want to lint C files that we didn't modify for + # this particular codebase (e.g. src/headers, src/klib, src/msgpack). However, + # we can lint all header files since they aren't "generated" like C files are. + MSG='Linting .c and .h' ; echo $MSG + cpplint --quiet --extensions=c,h --headers=h --recursive --filter=-readability/casting,-runtime/int,-build/include_subdir pandas/_libs/src/*.h pandas/_libs/src/parser pandas/_libs/ujson pandas/_libs/tslibs/src/datetime + RET=$(($RET + $?)) ; echo $MSG "DONE" + + # Imports - Check formatting using isort see setup.cfg for settings + MSG='Check import format using isort ' ; echo $MSG + isort --recursive --check-only pandas + RET=$(($RET + $?)) ; echo $MSG "DONE" + +fi + +### PATTERNS ### +if [[ -z "$CHECK" || "$CHECK" == "patterns" ]]; then + + # Check for imports from pandas.core.common instead of `import pandas.core.common as com` + MSG='Check for non-standard imports' ; echo $MSG + ! grep -R --include="*.py*" -E "from pandas.core.common import " pandas + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Check for pytest warns' ; echo $MSG + ! grep -r -E --include '*.py' 'pytest\.warns' pandas/tests/ + RET=$(($RET + $?)) ; echo $MSG "DONE" + + # Check for the following code in testing: `np.testing` and `np.array_equal` + MSG='Check for invalid testing' ; echo $MSG + ! grep -r -E --include '*.py' --exclude testing.py '(numpy|np)(\.testing|\.array_equal)' pandas/tests/ + RET=$(($RET + $?)) ; echo $MSG "DONE" + + # Check for the following code in the extension array base tests: `tm.assert_frame_equal` and `tm.assert_series_equal` + MSG='Check for invalid EA testing' ; echo $MSG + ! grep -r -E --include '*.py' --exclude base.py 'tm.assert_(series|frame)_equal' pandas/tests/extension/base + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Check for deprecated messages without sphinx directive' ; echo $MSG + ! grep -R --include="*.py" --include="*.pyx" -E "(DEPRECATED|DEPRECATE|Deprecated)(:|,|\.)" pandas + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Check for old-style classes' ; echo $MSG + ! grep -R --include="*.py" -E "class\s\S*[^)]:" pandas scripts + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Check for backticks incorrectly rendering because of missing spaces' ; echo $MSG + ! grep -R --include="*.rst" -E "[a-zA-Z0-9]\`\`?[a-zA-Z0-9]" doc/source/ + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Check for incorrect sphinx directives' ; echo $MSG + ! grep -R --include="*.py" --include="*.pyx" --include="*.rst" -E "\.\. (autosummary|contents|currentmodule|deprecated|function|image|important|include|ipython|literalinclude|math|module|note|raw|seealso|toctree|versionadded|versionchanged|warning):[^:]" ./pandas ./doc/source + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Check for modules that pandas should not import' ; echo $MSG + python -c " +import sys +import pandas + +blacklist = {'bs4', 'gcsfs', 'html5lib', 'ipython', 'jinja2' 'hypothesis', + 'lxml', 'numexpr', 'openpyxl', 'py', 'pytest', 's3fs', 'scipy', + 'tables', 'xlrd', 'xlsxwriter', 'xlwt'} +mods = blacklist & set(m.split('.')[0] for m in sys.modules) +if mods: + sys.stderr.write('pandas should not import: {}\n'.format(', '.join(mods))) + sys.exit(len(mods)) + " + RET=$(($RET + $?)) ; echo $MSG "DONE" + +fi + +### DOCTESTS ### +if [[ -z "$CHECK" || "$CHECK" == "doctests" ]]; then + + MSG='Doctests frame.py' ; echo $MSG + pytest -q --doctest-modules pandas/core/frame.py \ + -k"-axes -combine -itertuples -join -nunique -pivot_table -quantile -query -reindex -reindex_axis -replace -round -set_index -stack -to_stata" + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Doctests series.py' ; echo $MSG + pytest -q --doctest-modules pandas/core/series.py \ + -k"-nonzero -reindex -searchsorted -to_dict" + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Doctests generic.py' ; echo $MSG + pytest -q --doctest-modules pandas/core/generic.py \ + -k"-_set_axis_name -_xs -describe -droplevel -groupby -interpolate -pct_change -pipe -reindex -reindex_axis -resample -to_json -transpose -values -xs" + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Doctests top-level reshaping functions' ; echo $MSG + pytest -q --doctest-modules \ + pandas/core/reshape/concat.py \ + pandas/core/reshape/pivot.py \ + pandas/core/reshape/reshape.py \ + pandas/core/reshape/tile.py \ + -k"-crosstab -pivot_table -cut" + RET=$(($RET + $?)) ; echo $MSG "DONE" + + MSG='Doctests interval classes' ; echo $MSG + pytest --doctest-modules -v \ + pandas/core/indexes/interval.py \ + pandas/core/arrays/interval.py \ + -k"-from_arrays -from_breaks -from_intervals -from_tuples -get_loc -set_closed -to_tuples -interval_range" + RET=$(($RET + $?)) ; echo $MSG "DONE" + +fi + +exit $RET diff --git a/ci/circle-27-compat.yaml b/ci/deps/azure-27-compat.yaml similarity index 65% rename from ci/circle-27-compat.yaml rename to ci/deps/azure-27-compat.yaml index 81a48d4edf11c..5b726304cf414 100644 --- a/ci/circle-27-compat.yaml +++ b/ci/deps/azure-27-compat.yaml @@ -3,18 +3,18 @@ channels: - defaults - conda-forge dependencies: - - bottleneck=1.0.0 - - cython=0.24 + - bottleneck=1.2.0 + - cython=0.28.2 - jinja2=2.8 - - numexpr=2.4.4 # we test that we correctly don't use an unsupported numexpr - - numpy=1.9.2 - - openpyxl + - numexpr=2.6.1 + - numpy=1.12.0 + - openpyxl=2.5.5 - psycopg2 - - pytables=3.2.2 + - pytables=3.4.2 - python-dateutil=2.5.0 - python=2.7* - pytz=2013b - - scipy=0.14.0 + - scipy=0.18.1 - sqlalchemy=0.7.8 - xlrd=0.9.2 - xlsxwriter=0.5.2 @@ -26,3 +26,4 @@ dependencies: - html5lib==1.0b2 - beautifulsoup4==4.2.1 - pymysql==0.6.0 + - hypothesis>=3.58.0 diff --git a/ci/deps/azure-36-locale_slow.yaml b/ci/deps/azure-36-locale_slow.yaml new file mode 100644 index 0000000000000..7e40bd1a9979e --- /dev/null +++ b/ci/deps/azure-36-locale_slow.yaml @@ -0,0 +1,36 @@ +name: pandas +channels: + - defaults + - conda-forge +dependencies: + - beautifulsoup4 + - cython>=0.28.2 + - gcsfs + - html5lib + - ipython + - jinja2 + - lxml + - matplotlib + - nomkl + - numexpr + - numpy + - openpyxl + - psycopg2 + - pymysql + - pytables + - python-dateutil + - python=3.6* + - pytz + - s3fs + - scipy + - sqlalchemy + - xarray + - xlrd + - xlsxwriter + - xlwt + # universal + - pytest + - pytest-xdist + - moto + - pip: + - hypothesis>=3.58.0 diff --git a/ci/circle-36-locale_slow.yaml b/ci/deps/azure-37-locale.yaml similarity index 88% rename from ci/circle-36-locale_slow.yaml rename to ci/deps/azure-37-locale.yaml index cc852c1e2aeeb..59c8818eaef1e 100644 --- a/ci/circle-36-locale_slow.yaml +++ b/ci/deps/azure-37-locale.yaml @@ -4,7 +4,7 @@ channels: - conda-forge dependencies: - beautifulsoup4 - - cython + - cython>=0.28.2 - html5lib - ipython - jinja2 @@ -31,3 +31,5 @@ dependencies: - pytest - pytest-xdist - moto + - pip: + - hypothesis>=3.58.0 diff --git a/ci/travis-35-osx.yaml b/ci/deps/azure-macos-35.yaml similarity index 74% rename from ci/travis-35-osx.yaml rename to ci/deps/azure-macos-35.yaml index e74abac4c9775..6ccdc79d11b27 100644 --- a/ci/travis-35-osx.yaml +++ b/ci/deps/azure-macos-35.yaml @@ -4,15 +4,15 @@ channels: dependencies: - beautifulsoup4 - bottleneck - - cython + - cython>=0.28.2 - html5lib - jinja2 - lxml - - matplotlib + - matplotlib=2.2.0 - nomkl - numexpr - - numpy=1.10.4 - - openpyxl + - numpy=1.12.0 + - openpyxl=2.5.5 - pytables - python=3.5* - pytz @@ -25,3 +25,4 @@ dependencies: - pytest-xdist - pip: - python-dateutil==2.5.3 + - hypothesis>=3.58.0 diff --git a/ci/appveyor-27.yaml b/ci/deps/azure-windows-27.yaml similarity index 75% rename from ci/appveyor-27.yaml rename to ci/deps/azure-windows-27.yaml index 84107c605b14f..dc68129a5e6d3 100644 --- a/ci/appveyor-27.yaml +++ b/ci/deps/azure-windows-27.yaml @@ -6,14 +6,15 @@ dependencies: - beautifulsoup4 - bottleneck - dateutil + - gcsfs - html5lib - jinja2=2.8 - lxml - - matplotlib + - matplotlib=2.0.1 - numexpr - - numpy=1.10* + - numpy=1.12* - openpyxl - - pytables==3.2.2 + - pytables - python=2.7.* - pytz - s3fs @@ -23,7 +24,8 @@ dependencies: - xlsxwriter - xlwt # universal - - cython + - cython>=0.28.2 - pytest - pytest-xdist - moto + - hypothesis>=3.58.0 diff --git a/ci/appveyor-36.yaml b/ci/deps/azure-windows-36.yaml similarity index 73% rename from ci/appveyor-36.yaml rename to ci/deps/azure-windows-36.yaml index 5e370de39958a..af42545af7971 100644 --- a/ci/appveyor-36.yaml +++ b/ci/deps/azure-windows-36.yaml @@ -5,16 +5,17 @@ channels: dependencies: - blosc - bottleneck + - boost-cpp<1.67 - fastparquet - - feather-format - matplotlib - numexpr - - numpy=1.13* + - numpy=1.14* - openpyxl + - parquet-cpp - pyarrow - pytables - python-dateutil - - python=3.6.* + - python=3.6.6 - pytz - scipy - thrift=0.10* @@ -22,6 +23,7 @@ dependencies: - xlsxwriter - xlwt # universal - - cython + - cython>=0.28.2 - pytest - pytest-xdist + - hypothesis>=3.58.0 diff --git a/ci/circle-36-locale.yaml b/ci/deps/circle-36-locale.yaml similarity index 88% rename from ci/circle-36-locale.yaml rename to ci/deps/circle-36-locale.yaml index cc852c1e2aeeb..59c8818eaef1e 100644 --- a/ci/circle-36-locale.yaml +++ b/ci/deps/circle-36-locale.yaml @@ -4,7 +4,7 @@ channels: - conda-forge dependencies: - beautifulsoup4 - - cython + - cython>=0.28.2 - html5lib - ipython - jinja2 @@ -31,3 +31,5 @@ dependencies: - pytest - pytest-xdist - moto + - pip: + - hypothesis>=3.58.0 diff --git a/ci/travis-27-locale.yaml b/ci/deps/travis-27-locale.yaml similarity index 77% rename from ci/travis-27-locale.yaml rename to ci/deps/travis-27-locale.yaml index 1312c1296d46a..dc5580ae6d287 100644 --- a/ci/travis-27-locale.yaml +++ b/ci/deps/travis-27-locale.yaml @@ -3,11 +3,11 @@ channels: - defaults - conda-forge dependencies: - - bottleneck=1.0.0 - - cython=0.24 + - bottleneck=1.2.0 + - cython=0.28.2 - lxml - - matplotlib=1.4.3 - - numpy=1.9.2 + - matplotlib=2.0.0 + - numpy=1.12.0 - openpyxl=2.4.0 - python-dateutil - python-blosc @@ -22,6 +22,7 @@ dependencies: # universal - pytest - pytest-xdist + - hypothesis>=3.58.0 - pip: - html5lib==1.0b2 - beautifulsoup4==4.2.1 diff --git a/ci/travis-27.yaml b/ci/deps/travis-27.yaml similarity index 81% rename from ci/travis-27.yaml rename to ci/deps/travis-27.yaml index 22b993a2da886..28bee387a4f4a 100644 --- a/ci/travis-27.yaml +++ b/ci/deps/travis-27.yaml @@ -5,16 +5,15 @@ channels: dependencies: - beautifulsoup4 - bottleneck - - cython=0.24 + - cython=0.28.2 - fastparquet - - feather-format - - flake8=3.4.1 + - gcsfs - html5lib - ipython - jemalloc=4.5.0.post - jinja2=2.8 - lxml - - matplotlib + - matplotlib=2.2.2 - mock - nomkl - numexpr @@ -23,10 +22,11 @@ dependencies: - patsy - psycopg2 - py - - pyarrow=0.4.1 + - pyarrow=0.7.0 - PyCrypto - pymysql=0.6.3 - pytables + - blosc=1.14.3 - python-blosc - python-dateutil=2.5.0 - python=2.7* @@ -34,16 +34,16 @@ dependencies: - s3fs - scipy - sqlalchemy=0.9.6 - - xarray=0.8.0 + - xarray=0.9.6 - xlrd=0.9.2 - xlsxwriter=0.5.2 - xlwt=0.7.5 # universal - pytest - pytest-xdist - - moto + - moto==1.3.4 + - hypothesis>=3.58.0 - pip: - backports.lzma - - cpplint - pandas-gbq - pathlib diff --git a/ci/travis-36-doc.yaml b/ci/deps/travis-36-doc.yaml similarity index 86% rename from ci/travis-36-doc.yaml rename to ci/deps/travis-36-doc.yaml index c22dddbe0ba3f..ce095b887f189 100644 --- a/ci/travis-36-doc.yaml +++ b/ci/deps/travis-36-doc.yaml @@ -6,12 +6,12 @@ channels: dependencies: - beautifulsoup4 - bottleneck - - cython + - cython>=0.28.2 - fastparquet - - feather-format - html5lib + - hypothesis>=3.58.0 - ipykernel - - ipython + - ipython==6.5.0 - ipywidgets - lxml - matplotlib @@ -23,6 +23,7 @@ dependencies: - numpy=1.13* - openpyxl - pandoc + - pyarrow - pyqt - pytables - python-dateutil @@ -36,6 +37,7 @@ dependencies: - sphinx - sqlalchemy - statsmodels + - tzlocal - xarray - xlrd - xlsxwriter diff --git a/ci/travis-36-slow.yaml b/ci/deps/travis-36-slow.yaml similarity index 89% rename from ci/travis-36-slow.yaml rename to ci/deps/travis-36-slow.yaml index 6c475dc48723c..3157ecac3a902 100644 --- a/ci/travis-36-slow.yaml +++ b/ci/deps/travis-36-slow.yaml @@ -4,7 +4,7 @@ channels: - conda-forge dependencies: - beautifulsoup4 - - cython + - cython>=0.28.2 - html5lib - lxml - matplotlib @@ -28,3 +28,4 @@ dependencies: - pytest - pytest-xdist - moto + - hypothesis>=3.58.0 diff --git a/ci/travis-36.yaml b/ci/deps/travis-36.yaml similarity index 74% rename from ci/travis-36.yaml rename to ci/deps/travis-36.yaml index fe057e714761e..352717a842214 100644 --- a/ci/travis-36.yaml +++ b/ci/deps/travis-36.yaml @@ -4,13 +4,16 @@ channels: - conda-forge dependencies: - beautifulsoup4 - - cython + - cython>=0.28.2 - dask - fastparquet - - feather-format + - flake8>=3.5 + - flake8-comprehensions + - gcsfs - geopandas - html5lib - ipython + - isort - jinja2 - lxml - matplotlib @@ -18,12 +21,10 @@ dependencies: - numexpr - numpy - openpyxl - - pandas-datareader - psycopg2 - - pyarrow + - pyarrow=0.9.0 - pymysql - pytables - - python-dateutil - python-snappy - python=3.6* - pytz @@ -42,6 +43,10 @@ dependencies: - pytest-xdist - pytest-cov - moto + - hypothesis>=3.58.0 - pip: - brotlipy - coverage + - cpplint + - pandas-datareader + - python-dateutil diff --git a/ci/travis-36-numpydev.yaml b/ci/deps/travis-37-numpydev.yaml similarity index 83% rename from ci/travis-36-numpydev.yaml rename to ci/deps/travis-37-numpydev.yaml index 455d65feb4242..82c75b7c91b1f 100644 --- a/ci/travis-36-numpydev.yaml +++ b/ci/deps/travis-37-numpydev.yaml @@ -2,12 +2,13 @@ name: pandas channels: - defaults dependencies: - - python=3.6* + - python=3.7* - pytz - - Cython + - Cython>=0.28.2 # universal - pytest - pytest-xdist + - hypothesis>=3.58.0 - pip: - "git+git://github.com/dateutil/dateutil.git" - "-f https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com" diff --git a/ci/circle-35-ascii.yaml b/ci/deps/travis-37.yaml similarity index 56% rename from ci/circle-35-ascii.yaml rename to ci/deps/travis-37.yaml index 602c414b49bb2..7dbd85ac27df6 100644 --- a/ci/circle-35-ascii.yaml +++ b/ci/deps/travis-37.yaml @@ -1,13 +1,16 @@ name: pandas channels: - defaults + - conda-forge + - c3i_test dependencies: - - cython - - nomkl + - python=3.7 + - cython>=0.28.2 - numpy - python-dateutil - - python=3.5* + - nomkl + - pyarrow - pytz - # universal - pytest - pytest-xdist + - hypothesis>=3.58.0 diff --git a/ci/environment-dev.yaml b/ci/environment-dev.yaml index f9f9208519d61..3e69b1f725b24 100644 --- a/ci/environment-dev.yaml +++ b/ci/environment-dev.yaml @@ -3,13 +3,17 @@ channels: - defaults - conda-forge dependencies: - - Cython + - Cython>=0.28.2 - NumPy - flake8 + - flake8-comprehensions + - hypothesis>=3.58.0 + - isort - moto - - pytest>=3.1 + - pytest>=3.6 - python-dateutil>=2.5.0 - python=3 - pytz - setuptools>=24.2.0 - sphinx + - sphinxcontrib-spelling diff --git a/ci/incremental/build.cmd b/ci/incremental/build.cmd new file mode 100644 index 0000000000000..d2fd06d7d9e50 --- /dev/null +++ b/ci/incremental/build.cmd @@ -0,0 +1,10 @@ +@rem https://github.com/numba/numba/blob/master/buildscripts/incremental/build.cmd +call activate %CONDA_ENV% + +@rem Build numba extensions without silencing compile errors +python setup.py build_ext -q --inplace + +@rem Install pandas locally +python -m pip install -e . + +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/ci/incremental/build.sh b/ci/incremental/build.sh new file mode 100755 index 0000000000000..8f2301a3b7ef5 --- /dev/null +++ b/ci/incremental/build.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +source activate $CONDA_ENV + +# Make sure any error below is reported as such +set -v -e + +echo "[building extensions]" +python setup.py build_ext -q --inplace +python -m pip install -e . + +echo +echo "[show environment]" +conda list + +echo +echo "[done]" +exit 0 diff --git a/ci/incremental/install_miniconda.sh b/ci/incremental/install_miniconda.sh new file mode 100755 index 0000000000000..a47dfdb324b34 --- /dev/null +++ b/ci/incremental/install_miniconda.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -v -e + +# Install Miniconda +unamestr=`uname` +if [[ "$unamestr" == 'Linux' ]]; then + if [[ "$BITS32" == "yes" ]]; then + wget -q https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86.sh -O miniconda.sh + else + wget -q https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh + fi +elif [[ "$unamestr" == 'Darwin' ]]; then + wget -q https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O miniconda.sh +else + echo Error +fi +chmod +x miniconda.sh +./miniconda.sh -b diff --git a/ci/incremental/setup_conda_environment.cmd b/ci/incremental/setup_conda_environment.cmd new file mode 100644 index 0000000000000..35595ffb03695 --- /dev/null +++ b/ci/incremental/setup_conda_environment.cmd @@ -0,0 +1,21 @@ +@rem https://github.com/numba/numba/blob/master/buildscripts/incremental/setup_conda_environment.cmd +@rem The cmd /C hack circumvents a regression where conda installs a conda.bat +@rem script in non-root environments. +set CONDA_INSTALL=cmd /C conda install -q -y +set PIP_INSTALL=pip install -q + +@echo on + +@rem Deactivate any environment +call deactivate +@rem Display root environment (for debugging) +conda list +@rem Clean up any left-over from a previous build +conda remove --all -q -y -n %CONDA_ENV% +@rem Scipy, CFFI, jinja2 and IPython are optional dependencies, but exercised in the test suite +conda env create -n %CONDA_ENV% --file=ci\deps\azure-windows-%CONDA_PY%.yaml + +call activate %CONDA_ENV% +conda list + +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/ci/incremental/setup_conda_environment.sh b/ci/incremental/setup_conda_environment.sh new file mode 100755 index 0000000000000..f3ac99d5e7c5a --- /dev/null +++ b/ci/incremental/setup_conda_environment.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +set -v -e + +CONDA_INSTALL="conda install -q -y" +PIP_INSTALL="pip install -q" + +# Deactivate any environment +source deactivate +# Display root environment (for debugging) +conda list +# Clean up any left-over from a previous build +# (note workaround for https://github.com/conda/conda/issues/2679: +# `conda env remove` issue) +conda remove --all -q -y -n $CONDA_ENV + +echo +echo "[create env]" +time conda env create -q -n "${CONDA_ENV}" --file="${ENV_FILE}" || exit 1 + +# Activate first +set +v +source activate $CONDA_ENV +set -v + +# remove any installed pandas package +# w/o removing anything else +echo +echo "[removing installed pandas]" +conda remove pandas -y --force || true +pip uninstall -y pandas || true + +echo +echo "[no installed pandas]" +conda list pandas + +if [ -n "$LOCALE_OVERRIDE" ]; then + sudo locale-gen "$LOCALE_OVERRIDE" +fi + +# # Install the compiler toolchain +# if [[ $(uname) == Linux ]]; then +# if [[ "$CONDA_SUBDIR" == "linux-32" || "$BITS32" == "yes" ]] ; then +# $CONDA_INSTALL gcc_linux-32 gxx_linux-32 +# else +# $CONDA_INSTALL gcc_linux-64 gxx_linux-64 +# fi +# elif [[ $(uname) == Darwin ]]; then +# $CONDA_INSTALL clang_osx-64 clangxx_osx-64 +# # Install llvm-openmp and intel-openmp on OSX too +# $CONDA_INSTALL llvm-openmp intel-openmp +# fi diff --git a/ci/install.ps1 b/ci/install.ps1 deleted file mode 100644 index 64ec7f81884cd..0000000000000 --- a/ci/install.ps1 +++ /dev/null @@ -1,92 +0,0 @@ -# Sample script to install Miniconda under Windows -# Authors: Olivier Grisel, Jonathan Helmus and Kyle Kastner, Robert McGibbon -# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ - -$MINICONDA_URL = "http://repo.continuum.io/miniconda/" - - -function DownloadMiniconda ($python_version, $platform_suffix) { - $webclient = New-Object System.Net.WebClient - $filename = "Miniconda3-latest-Windows-" + $platform_suffix + ".exe" - $url = $MINICONDA_URL + $filename - - $basedir = $pwd.Path + "\" - $filepath = $basedir + $filename - if (Test-Path $filename) { - Write-Host "Reusing" $filepath - return $filepath - } - - # Download and retry up to 3 times in case of network transient errors. - Write-Host "Downloading" $filename "from" $url - $retry_attempts = 2 - for($i=0; $i -lt $retry_attempts; $i++){ - try { - $webclient.DownloadFile($url, $filepath) - break - } - Catch [Exception]{ - Start-Sleep 1 - } - } - if (Test-Path $filepath) { - Write-Host "File saved at" $filepath - } else { - # Retry once to get the error message if any at the last try - $webclient.DownloadFile($url, $filepath) - } - return $filepath -} - - -function InstallMiniconda ($python_version, $architecture, $python_home) { - Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home - if (Test-Path $python_home) { - Write-Host $python_home "already exists, skipping." - return $false - } - if ($architecture -match "32") { - $platform_suffix = "x86" - } else { - $platform_suffix = "x86_64" - } - - $filepath = DownloadMiniconda $python_version $platform_suffix - Write-Host "Installing" $filepath "to" $python_home - $install_log = $python_home + ".log" - $args = "/S /D=$python_home" - Write-Host $filepath $args - Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru - if (Test-Path $python_home) { - Write-Host "Python $python_version ($architecture) installation complete" - } else { - Write-Host "Failed to install Python in $python_home" - Get-Content -Path $install_log - Exit 1 - } -} - - -function InstallCondaPackages ($python_home, $spec) { - $conda_path = $python_home + "\Scripts\conda.exe" - $args = "install --yes " + $spec - Write-Host ("conda " + $args) - Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru -} - -function UpdateConda ($python_home) { - $conda_path = $python_home + "\Scripts\conda.exe" - Write-Host "Updating conda..." - $args = "update --yes conda" - Write-Host $conda_path $args - Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru -} - - -function main () { - InstallMiniconda "3.5" $env:PYTHON_ARCH $env:CONDA_ROOT - UpdateConda $env:CONDA_ROOT - InstallCondaPackages $env:CONDA_ROOT "conda-build jinja2 anaconda-client" -} - -main diff --git a/ci/install_db_circle.sh b/ci/install_db_circle.sh deleted file mode 100755 index a00f74f009f54..0000000000000 --- a/ci/install_db_circle.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -echo "installing dbs" -mysql -e 'create database pandas_nosetest;' -psql -c 'create database pandas_nosetest;' -U postgres - -echo "done" -exit 0 diff --git a/ci/lint.sh b/ci/lint.sh deleted file mode 100755 index 2cbf6f7ae52a9..0000000000000 --- a/ci/lint.sh +++ /dev/null @@ -1,181 +0,0 @@ -#!/bin/bash - -echo "inside $0" - -source activate pandas - -RET=0 - -if [ "$LINT" ]; then - - # pandas/_libs/src is C code, so no need to search there. - echo "Linting *.py" - flake8 pandas --filename=*.py --exclude pandas/_libs/src - if [ $? -ne "0" ]; then - RET=1 - fi - echo "Linting *.py DONE" - - echo "Linting setup.py" - flake8 setup.py - if [ $? -ne "0" ]; then - RET=1 - fi - echo "Linting setup.py DONE" - - echo "Linting asv_bench/benchmarks/" - flake8 asv_bench/benchmarks/ --exclude=asv_bench/benchmarks/*.py --ignore=F811 - if [ $? -ne "0" ]; then - RET=1 - fi - echo "Linting asv_bench/benchmarks/*.py DONE" - - echo "Linting scripts/*.py" - flake8 scripts --filename=*.py - if [ $? -ne "0" ]; then - RET=1 - fi - echo "Linting scripts/*.py DONE" - - echo "Linting doc scripts" - flake8 doc/make.py doc/source/conf.py - if [ $? -ne "0" ]; then - RET=1 - fi - echo "Linting doc scripts DONE" - - echo "Linting *.pyx" - flake8 pandas --filename=*.pyx --select=E501,E302,E203,E111,E114,E221,E303,E128,E231,E126,E265,E305,E301,E127,E261,E271,E129,W291,E222,E241,E123,F403 - if [ $? -ne "0" ]; then - RET=1 - fi - echo "Linting *.pyx DONE" - - echo "Linting *.pxi.in" - for path in 'src' - do - echo "linting -> pandas/$path" - flake8 pandas/$path --filename=*.pxi.in --select=E501,E302,E203,E111,E114,E221,E303,E231,E126,F403 - if [ $? -ne "0" ]; then - RET=1 - fi - done - echo "Linting *.pxi.in DONE" - - echo "Linting *.pxd" - for path in '_libs' - do - echo "linting -> pandas/$path" - flake8 pandas/$path --filename=*.pxd --select=E501,E302,E203,E111,E114,E221,E303,E231,E126,F403 - if [ $? -ne "0" ]; then - RET=1 - fi - done - echo "Linting *.pxd DONE" - - # readability/casting: Warnings about C casting instead of C++ casting - # runtime/int: Warnings about using C number types instead of C++ ones - # build/include_subdir: Warnings about prefacing included header files with directory - - # We don't lint all C files because we don't want to lint any that are built - # from Cython files nor do we want to lint C files that we didn't modify for - # this particular codebase (e.g. src/headers, src/klib, src/msgpack). However, - # we can lint all header files since they aren't "generated" like C files are. - echo "Linting *.c and *.h" - for path in '*.h' 'period_helper.c' 'datetime' 'parser' 'ujson' - do - echo "linting -> pandas/_libs/src/$path" - cpplint --quiet --extensions=c,h --headers=h --filter=-readability/casting,-runtime/int,-build/include_subdir --recursive pandas/_libs/src/$path - if [ $? -ne "0" ]; then - RET=1 - fi - done - echo "Linting *.c and *.h DONE" - - echo "Check for invalid testing" - - # Check for the following code in testing: - # - # np.testing - # np.array_equal - grep -r -E --include '*.py' --exclude testing.py '(numpy|np)(\.testing|\.array_equal)' pandas/tests/ - - if [ $? = "0" ]; then - RET=1 - fi - - # Check for pytest.warns - grep -r -E --include '*.py' 'pytest\.warns' pandas/tests/ - - if [ $? = "0" ]; then - RET=1 - fi - - # Check for the following code in the extension array base tests - # tm.assert_frame_equal - # tm.assert_series_equal - grep -r -E --include '*.py' --exclude base.py 'tm.assert_(series|frame)_equal' pandas/tests/extension/base - - if [ $? = "0" ]; then - RET=1 - fi - - echo "Check for invalid testing DONE" - - # Check for imports from pandas.core.common instead - # of `import pandas.core.common as com` - echo "Check for non-standard imports" - grep -R --include="*.py*" -E "from pandas.core.common import " pandas - if [ $? = "0" ]; then - RET=1 - fi - echo "Check for non-standard imports DONE" - - echo "Check for use of lists instead of generators in built-in Python functions" - - # Example: Avoid `any([i for i in some_iterator])` in favor of `any(i for i in some_iterator)` - # - # Check the following functions: - # any(), all(), sum(), max(), min(), list(), dict(), set(), frozenset(), tuple(), str.join() - grep -R --include="*.py*" -E "[^_](any|all|sum|max|min|list|dict|set|frozenset|tuple|join)\(\[.* for .* in .*\]\)" pandas - - if [ $? = "0" ]; then - RET=1 - fi - echo "Check for use of lists instead of generators in built-in Python functions DONE" - - echo "Check for incorrect sphinx directives" - SPHINX_DIRECTIVES=$(echo \ - "autosummary|contents|currentmodule|deprecated|function|image|"\ - "important|include|ipython|literalinclude|math|module|note|raw|"\ - "seealso|toctree|versionadded|versionchanged|warning" | tr -d "[:space:]") - for path in './pandas' './doc/source' - do - grep -R --include="*.py" --include="*.pyx" --include="*.rst" -E "\.\. ($SPHINX_DIRECTIVES):[^:]" $path - if [ $? = "0" ]; then - RET=1 - fi - done - echo "Check for incorrect sphinx directives DONE" - - echo "Check for deprecated messages without sphinx directive" - grep -R --include="*.py" --include="*.pyx" -E "(DEPRECATED|DEPRECATE|Deprecated)(:|,|\.)" pandas - - if [ $? = "0" ]; then - RET=1 - fi - echo "Check for deprecated messages without sphinx directive DONE" - - echo "Check for old-style classes" - grep -R --include="*.py" -E "class\s\S*[^)]:" pandas scripts - - if [ $? = "0" ]; then - RET=1 - fi - echo "Check for old-style classes DONE" - -else - echo "NOT Linting" -fi - -exit $RET diff --git a/ci/print_skipped.py b/ci/print_skipped.py index dd2180f6eeb19..67bc7b556cd43 100755 --- a/ci/print_skipped.py +++ b/ci/print_skipped.py @@ -10,7 +10,7 @@ def parse_results(filename): root = tree.getroot() skipped = [] - current_class = old_class = '' + current_class = '' i = 1 assert i - 1 == len(skipped) for el in root.findall('testcase'): @@ -24,7 +24,9 @@ def parse_results(filename): out = '' if old_class != current_class: ndigits = int(math.log(i, 10) + 1) - out += ('-' * (len(name + msg) + 4 + ndigits) + '\n') # 4 for : + space + # + space + + # 4 for : + space + # + space + out += ('-' * (len(name + msg) + 4 + ndigits) + '\n') out += '#{i} {name}: {msg}'.format(i=i, name=name, msg=msg) skipped.append(out) i += 1 diff --git a/ci/print_versions.py b/ci/print_versions.py index 8be795174d76d..a2c93748b0388 100755 --- a/ci/print_versions.py +++ b/ci/print_versions.py @@ -18,7 +18,8 @@ def show_versions(as_json=False): from optparse import OptionParser parser = OptionParser() parser.add_option("-j", "--json", metavar="FILE", nargs=1, - help="Save output as JSON into file, pass in '-' to output to stdout") + help="Save output as JSON into file, " + "pass in '-' to output to stdout") (options, args) = parser.parse_args() diff --git a/ci/requirements-optional-conda.txt b/ci/requirements-optional-conda.txt index e8cfcdf80f2e8..8758c8154abca 100644 --- a/ci/requirements-optional-conda.txt +++ b/ci/requirements-optional-conda.txt @@ -1,26 +1,27 @@ beautifulsoup4>=4.2.1 blosc -bottleneck -fastparquet -feather-format +bottleneck>=1.2.0 +fastparquet>=0.1.2 +gcsfs html5lib ipython>=5.6.0 ipykernel jinja2 lxml -matplotlib +matplotlib>=2.0.0 nbsphinx -numexpr +numexpr>=2.6.1 openpyxl -pyarrow +pyarrow>=0.7.0 pymysql -pytables +pytables>=3.4.2 pytest-cov pytest-xdist s3fs -scipy +scipy>=0.18.1 seaborn sqlalchemy +statsmodels xarray xlrd xlsxwriter diff --git a/ci/requirements-optional-pip.txt b/ci/requirements-optional-pip.txt index 877c52fa0b4fd..62f1c555d8544 100644 --- a/ci/requirements-optional-pip.txt +++ b/ci/requirements-optional-pip.txt @@ -2,27 +2,28 @@ # Do not modify directly beautifulsoup4>=4.2.1 blosc -bottleneck -fastparquet -feather-format +bottleneck>=1.2.0 +fastparquet>=0.1.2 +gcsfs html5lib ipython>=5.6.0 ipykernel jinja2 lxml -matplotlib +matplotlib>=2.0.0 nbsphinx -numexpr +numexpr>=2.6.1 openpyxl -pyarrow +pyarrow>=0.7.0 pymysql -tables +pytables>=3.4.2 pytest-cov pytest-xdist s3fs -scipy +scipy>=0.18.1 seaborn sqlalchemy +statsmodels xarray xlrd xlsxwriter diff --git a/ci/requirements_dev.txt b/ci/requirements_dev.txt index 3430e778a4573..6a8b8d64d943b 100644 --- a/ci/requirements_dev.txt +++ b/ci/requirements_dev.txt @@ -1,11 +1,15 @@ # This file was autogenerated by scripts/convert_deps.py # Do not modify directly -Cython +Cython>=0.28.2 NumPy flake8 +flake8-comprehensions +hypothesis>=3.58.0 +isort moto -pytest>=3.1 +pytest>=3.6 python-dateutil>=2.5.0 pytz setuptools>=24.2.0 sphinx +sphinxcontrib-spelling \ No newline at end of file diff --git a/ci/run_circle.sh b/ci/run_circle.sh deleted file mode 100755 index 435985bd42148..0000000000000 --- a/ci/run_circle.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -echo "[running tests]" -export PATH="$MINICONDA_DIR/bin:$PATH" - -source activate pandas - -echo "pytest --strict --junitxml=$CIRCLE_TEST_REPORTS/reports/junit.xml $@ pandas" -pytest --strict --junitxml=$CIRCLE_TEST_REPORTS/reports/junit.xml $@ pandas diff --git a/ci/script_multi.sh b/ci/script_multi.sh index 2b2d4d5488b91..e56d5da7232b2 100755 --- a/ci/script_multi.sh +++ b/ci/script_multi.sh @@ -27,17 +27,17 @@ if [ "$DOC" ]; then echo "We are not running pytest as this is a doc-build" elif [ "$COVERAGE" ]; then - echo pytest -s -n 2 -m "not single" --cov=pandas --cov-report xml:/tmp/cov-multiple.xml --junitxml=/tmp/multiple.xml --strict $TEST_ARGS pandas - pytest -s -n 2 -m "not single" --cov=pandas --cov-report xml:/tmp/cov-multiple.xml --junitxml=/tmp/multiple.xml --strict $TEST_ARGS pandas + echo pytest -s -n 2 -m "not single" --durations=10 --cov=pandas --cov-report xml:/tmp/cov-multiple.xml --junitxml=test-data-multiple.xml --strict $TEST_ARGS pandas + pytest -s -n 2 -m "not single" --durations=10 --cov=pandas --cov-report xml:/tmp/cov-multiple.xml --junitxml=test-data-multiple.xml --strict $TEST_ARGS pandas elif [ "$SLOW" ]; then TEST_ARGS="--only-slow --skip-network" - echo pytest -r xX -m "not single and slow" -v --junitxml=/tmp/multiple.xml --strict $TEST_ARGS pandas - pytest -r xX -m "not single and slow" -v --junitxml=/tmp/multiple.xml --strict $TEST_ARGS pandas + echo pytest -m "not single and slow" -v --durations=10 --junitxml=test-data-multiple.xml --strict $TEST_ARGS pandas + pytest -m "not single and slow" -v --durations=10 --junitxml=test-data-multiple.xml --strict $TEST_ARGS pandas else - echo pytest -n 2 -r xX -m "not single" --junitxml=/tmp/multiple.xml --strict $TEST_ARGS pandas - pytest -n 2 -r xX -m "not single" --junitxml=/tmp/multiple.xml --strict $TEST_ARGS pandas # TODO: doctest + echo pytest -n 2 -m "not single" --durations=10 --junitxml=test-data-multiple.xml --strict $TEST_ARGS pandas + pytest -n 2 -m "not single" --durations=10 --junitxml=test-data-multiple.xml --strict $TEST_ARGS pandas # TODO: doctest fi diff --git a/ci/script_single.sh b/ci/script_single.sh index f376c920ac71b..ea0d48bc2da8a 100755 --- a/ci/script_single.sh +++ b/ci/script_single.sh @@ -5,8 +5,9 @@ echo "[script_single]" source activate pandas if [ -n "$LOCALE_OVERRIDE" ]; then + echo "Setting LC_ALL and LANG to $LOCALE_OVERRIDE" export LC_ALL="$LOCALE_OVERRIDE"; - echo "Setting LC_ALL to $LOCALE_OVERRIDE" + export LANG="$LOCALE_OVERRIDE"; pycmd='import pandas; print("pandas detected console encoding: %s" % pandas.get_option("display.encoding"))' python -c "$pycmd" @@ -25,12 +26,13 @@ if [ "$DOC" ]; then echo "We are not running pytest as this is a doc-build" elif [ "$COVERAGE" ]; then - echo pytest -s -m "single" --strict --cov=pandas --cov-report xml:/tmp/cov-single.xml --junitxml=/tmp/single.xml $TEST_ARGS pandas - pytest -s -m "single" --strict --cov=pandas --cov-report xml:/tmp/cov-single.xml --junitxml=/tmp/single.xml $TEST_ARGS pandas - + echo pytest -s -m "single" --durations=10 --strict --cov=pandas --cov-report xml:/tmp/cov-single.xml --junitxml=test-data-single.xml $TEST_ARGS pandas + pytest -s -m "single" --durations=10 --strict --cov=pandas --cov-report xml:/tmp/cov-single.xml --junitxml=test-data-single.xml $TEST_ARGS pandas + echo pytest -s --strict scripts + pytest -s --strict scripts else - echo pytest -m "single" -r xX --junitxml=/tmp/single.xml --strict $TEST_ARGS pandas - pytest -m "single" -r xX --junitxml=/tmp/single.xml --strict $TEST_ARGS pandas # TODO: doctest + echo pytest -m "single" --durations=10 --junitxml=test-data-single.xml --strict $TEST_ARGS pandas + pytest -m "single" --durations=10 --junitxml=test-data-single.xml --strict $TEST_ARGS pandas fi diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 66415defba6fe..0000000000000 --- a/circle.yml +++ /dev/null @@ -1,38 +0,0 @@ -machine: - environment: - # these are globally set - MINICONDA_DIR: /home/ubuntu/miniconda3 - - -database: - override: - - ./ci/install_db_circle.sh - - -checkout: - post: - # since circleci does a shallow fetch - # we need to populate our tags - - git fetch --depth=1000 - - -dependencies: - override: - - > - case $CIRCLE_NODE_INDEX in - 0) - sudo apt-get install language-pack-it && ./ci/install_circle.sh JOB="2.7_COMPAT" ENV_FILE="ci/circle-27-compat.yaml" LOCALE_OVERRIDE="it_IT.UTF-8" ;; - 1) - sudo apt-get install language-pack-zh-hans && ./ci/install_circle.sh JOB="3.6_LOCALE" ENV_FILE="ci/circle-36-locale.yaml" LOCALE_OVERRIDE="zh_CN.UTF-8" ;; - 2) - sudo apt-get install language-pack-zh-hans && ./ci/install_circle.sh JOB="3.6_LOCALE_SLOW" ENV_FILE="ci/circle-36-locale_slow.yaml" LOCALE_OVERRIDE="zh_CN.UTF-8" ;; - 3) - ./ci/install_circle.sh JOB="3.5_ASCII" ENV_FILE="ci/circle-35-ascii.yaml" LOCALE_OVERRIDE="C" ;; - esac - - ./ci/show_circle.sh - - -test: - override: - - case $CIRCLE_NODE_INDEX in 0) ./ci/run_circle.sh --skip-slow --skip-network ;; 1) ./ci/run_circle.sh --only-slow --skip-network ;; 2) ./ci/run_circle.sh --skip-slow --skip-network ;; 3) ./ci/run_circle.sh --skip-slow --skip-network ;; esac: - parallel: true diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 86bed996c8aab..f92090fecccf3 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -12,22 +12,28 @@ source: requirements: build: + - {{ compiler('c') }} + - {{ compiler('cxx') }} + host: - python + - pip - cython - - numpy 1.11.* + - numpy - setuptools >=3.3 - python-dateutil >=2.5.0 - pytz - run: - - python - - numpy >=1.11.* + - python {{ python }} + - {{ pin_compatible('numpy') }} - python-dateutil >=2.5.0 - pytz test: - imports: - - pandas + requires: + - pytest + commands: + - python -c "import pandas; pandas.test()" + about: home: http://pandas.pydata.org diff --git a/doc/cheatsheet/Pandas_Cheat_Sheet_JA.pdf b/doc/cheatsheet/Pandas_Cheat_Sheet_JA.pdf new file mode 100644 index 0000000000000000000000000000000000000000..daa65a944e68acfc9ce5e4b47929d806ae6ba41b GIT binary patch literal 206153 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�ci(J|R{ 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
1Dvm>^3fCy4Q$aJo|lBt|rbomhcma08#`% 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 bk2yFx0Y2al*9kC;i;ErY-n~ciVrc&d3i$UZ literal 0 HcmV?d00001 diff --git a/doc/cheatsheet/Pandas_Cheat_Sheet_JA.pptx b/doc/cheatsheet/Pandas_Cheat_Sheet_JA.pptx new file mode 100644 index 0000000000000000000000000000000000000000..6270a71e20ee88f5020a76f139f404f18f4bfc30 GIT binary patch literal 76495 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

Ql*HwboAFVY{v2e%FIi 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 DocBuilder(num_jobs=4)._sphinx_build('html') """ - if kind not in ('html', 'latex'): - raise ValueError('kind must be html or latex, not {}'.format(kind)) + if kind not in ('html', 'latex', 'spelling'): + raise ValueError('kind must be html, latex or ' + 'spelling, not {}'.format(kind)) self._run_os('sphinx-build', '-j{}'.format(self.num_jobs), '-b{}'.format(kind), '-{}'.format( 'v' * self.verbosity) if self.verbosity else '', - '-d{}'.format(os.path.join(BUILD_PATH, 'doctrees')), + '-d"{}"'.format(os.path.join(BUILD_PATH, 'doctrees')), '-Dexclude_patterns={}'.format(self.exclude_patterns), - SOURCE_PATH, - os.path.join(BUILD_PATH, kind)) + '"{}"'.format(SOURCE_PATH), + '"{}"'.format(os.path.join(BUILD_PATH, kind))) def _open_browser(self): base_url = os.path.join('file://', DOC_PATH, 'build', 'html') @@ -304,6 +305,18 @@ def zip_html(self): '-q', *fnames) + def spellcheck(self): + """Spell check the documentation.""" + self._sphinx_build('spelling') + output_location = os.path.join('build', 'spelling', 'output.txt') + with open(output_location) as output: + lines = output.readlines() + if lines: + raise SyntaxError( + 'Found misspelled words.' + ' Check pandas/doc/build/spelling/output.txt' + ' for more details.') + def main(): cmds = [method for method in dir(DocBuilder) if not method.startswith('_')] @@ -350,6 +363,10 @@ def main(): sys.path.append(args.python_path) globals()['pandas'] = importlib.import_module('pandas') + # Set the matplotlib backend to the non-interactive Agg backend for all + # child processes. + os.environ['MPLBACKEND'] = 'module://matplotlib.backends.backend_agg' + builder = DocBuilder(args.num_jobs, not args.no_api, args.single, args.verbosity) getattr(builder, args.command)() diff --git a/doc/source/advanced.rst b/doc/source/advanced.rst index ec517d3e07bdf..608e2c8e72ded 100644 --- a/doc/source/advanced.rst +++ b/doc/source/advanced.rst @@ -15,13 +15,14 @@ MultiIndex / Advanced Indexing ****************************** -This section covers indexing with a ``MultiIndex`` and more advanced indexing features. +This section covers :ref:`indexing with a MultiIndex ` +and :ref:`other advanced indexing features `. See the :ref:`Indexing and Selecting Data ` for general indexing documentation. .. warning:: - Whether a copy or a reference is returned for a setting operation, may + Whether a copy or a reference is returned for a setting operation may depend on the context. This is sometimes called ``chained assignment`` and should be avoided. See :ref:`Returning a View versus Copy `. @@ -37,7 +38,7 @@ Hierarchical / Multi-level indexing is very exciting as it opens the door to som quite sophisticated data analysis and manipulation, especially for working with higher dimensional data. In essence, it enables you to store and manipulate data with an arbitrary number of dimensions in lower dimensional data -structures like Series (1d) and DataFrame (2d). +structures like ``Series`` (1d) and ``DataFrame`` (2d). In this section, we will show what exactly we mean by "hierarchical" indexing and how it integrates with all of the pandas indexing functionality @@ -51,13 +52,13 @@ See the :ref:`cookbook` for some advanced strategies. Creating a MultiIndex (hierarchical index) object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``MultiIndex`` object is the hierarchical analogue of the standard -``Index`` object which typically stores the axis labels in pandas objects. You +The :class:`MultiIndex` object is the hierarchical analogue of the standard +:class:`Index` object which typically stores the axis labels in pandas objects. You can think of ``MultiIndex`` as an array of tuples where each tuple is unique. A ``MultiIndex`` can be created from a list of arrays (using -``MultiIndex.from_arrays``), an array of tuples (using -``MultiIndex.from_tuples``), or a crossed set of iterables (using -``MultiIndex.from_product``). The ``Index`` constructor will attempt to return +:meth:`MultiIndex.from_arrays`), an array of tuples (using +:meth:`MultiIndex.from_tuples`), or a crossed set of iterables (using +:meth:`MultiIndex.from_product`). The ``Index`` constructor will attempt to return a ``MultiIndex`` when it is passed a list of tuples. The following examples demonstrate different ways to initialize MultiIndexes. @@ -76,15 +77,15 @@ demonstrate different ways to initialize MultiIndexes. s When you want every pairing of the elements in two iterables, it can be easier -to use the ``MultiIndex.from_product`` function: +to use the :meth:`MultiIndex.from_product` method: .. ipython:: python iterables = [['bar', 'baz', 'foo', 'qux'], ['one', 'two']] pd.MultiIndex.from_product(iterables, names=['first', 'second']) -As a convenience, you can pass a list of arrays directly into Series or -DataFrame to construct a MultiIndex automatically: +As a convenience, you can pass a list of arrays directly into ``Series`` or +``DataFrame`` to construct a ``MultiIndex`` automatically: .. ipython:: python @@ -140,7 +141,7 @@ may wish to generate your own ``MultiIndex`` when preparing the data set. Reconstructing the level labels ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The method ``get_level_values`` will return a vector of the labels for each +The method :meth:`~MultiIndex.get_level_values` will return a vector of the labels for each location at a particular level: .. ipython:: python @@ -172,7 +173,7 @@ Defined Levels ~~~~~~~~~~~~~~ The repr of a ``MultiIndex`` shows all the defined levels of an index, even -if the they are not actually used. When slicing an index, you may notice this. +if they are not actually used. When slicing an index, you may notice this. For example: .. ipython:: python @@ -183,7 +184,7 @@ For example: This is done to avoid a recomputation of the levels in order to make slicing highly performant. If you want to see only the used levels, you can use the -:func:`MultiIndex.get_level_values` method. +:meth:`~MultiIndex.get_level_values` method. .. ipython:: python @@ -193,7 +194,7 @@ highly performant. If you want to see only the used levels, you can use the df[['foo','qux']].columns.get_level_values(0) To reconstruct the ``MultiIndex`` with only the used levels, the -``remove_unused_levels`` method may be used. +:meth:`~MultiIndex.remove_unused_levels` method may be used. .. versionadded:: 0.20.0 @@ -213,8 +214,8 @@ tuples: s + s[:-2] s + s[::2] -``reindex`` can be called with another ``MultiIndex``, or even a list or array -of tuples: +The :meth:`~DataFrame.reindex` method of ``Series``/``DataFrames`` can be +called with another ``MultiIndex``, or even a list or array of tuples: .. ipython:: python @@ -342,7 +343,7 @@ As usual, **both sides** of the slicers are included as this is label indexing. columns=micolumns).sort_index().sort_index(axis=1) dfmi -Basic multi-index slicing using slices, lists, and labels. +Basic MultiIndex slicing using slices, lists, and labels. .. ipython:: python @@ -379,7 +380,7 @@ slicers on a single axis. dfmi.loc(axis=0)[:, :, ['C1', 'C3']] -Furthermore you can *set* the values using the following methods. +Furthermore, you can *set* the values using the following methods. .. ipython:: python @@ -400,8 +401,8 @@ You can use a right-hand-side of an alignable object as well. Cross-section ~~~~~~~~~~~~~ -The ``xs`` method of ``DataFrame`` additionally takes a level argument to make -selecting data at a particular level of a MultiIndex easier. +The :meth:`~DataFrame.xs` method of ``DataFrame`` additionally takes a level argument to make +selecting data at a particular level of a ``MultiIndex`` easier. .. ipython:: python @@ -413,7 +414,7 @@ selecting data at a particular level of a MultiIndex easier. # using the slicers df.loc[(slice(None),'one'),:] -You can also select on the columns with :meth:`~pandas.MultiIndex.xs`, by +You can also select on the columns with ``xs``, by providing the axis argument. .. ipython:: python @@ -426,7 +427,7 @@ providing the axis argument. # using the slicers df.loc[:,(slice(None),'one')] -:meth:`~pandas.MultiIndex.xs` also allows selection with multiple keys. +``xs`` also allows selection with multiple keys. .. ipython:: python @@ -437,7 +438,7 @@ providing the axis argument. # using the slicers df.loc[:,('bar','one')] -You can pass ``drop_level=False`` to :meth:`~pandas.MultiIndex.xs` to retain +You can pass ``drop_level=False`` to ``xs`` to retain the level that was selected. .. ipython:: python @@ -460,9 +461,9 @@ Compare the above with the result using ``drop_level=True`` (the default value). Advanced reindexing and alignment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The parameter ``level`` has been added to the ``reindex`` and ``align`` methods -of pandas objects. This is useful to broadcast values across a level. For -instance: +Using the parameter ``level`` in the :meth:`~DataFrame.reindex` and +:meth:`~DataFrame.align` methods of pandas objects is useful to broadcast +values across a level. For instance: .. ipython:: python @@ -480,10 +481,10 @@ instance: df2_aligned -Swapping levels with :meth:`~pandas.MultiIndex.swaplevel` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Swapping levels with ``swaplevel`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``swaplevel`` function can switch the order of two levels: +The :meth:`~MultiIndex.swaplevel` method can switch the order of two levels: .. ipython:: python @@ -492,21 +493,62 @@ The ``swaplevel`` function can switch the order of two levels: .. _advanced.reorderlevels: -Reordering levels with :meth:`~pandas.MultiIndex.reorder_levels` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Reordering levels with ``reorder_levels`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``reorder_levels`` function generalizes the ``swaplevel`` function, -allowing you to permute the hierarchical index levels in one step: +The :meth:`~MultiIndex.reorder_levels` method generalizes the ``swaplevel`` +method, allowing you to permute the hierarchical index levels in one step: .. ipython:: python df[:5].reorder_levels([1,0], axis=0) -Sorting a :class:`~pandas.MultiIndex` -------------------------------------- +.. _advanced.index_names: -For MultiIndex-ed objects to be indexed and sliced effectively, they need -to be sorted. As with any index, you can use ``sort_index``. +Renaming names of an ``Index`` or ``MultiIndex`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :meth:`~DataFrame.rename` method is used to rename the labels of a +``MultiIndex``, and is typically used to rename the columns of a ``DataFrame``. +The ``columns`` argument of ``rename`` allows a dictionary to be specified +that includes only the columns you wish to rename. + +.. ipython:: python + + df.rename(columns={0: "col0", 1: "col1"}) + +This method can also be used to rename specific labels of the main index +of the ``DataFrame``. + +.. ipython:: python + + df.rename(index={"one" : "two", "y" : "z"}) + +The :meth:`~DataFrame.rename_axis` method is used to rename the name of a +``Index`` or ``MultiIndex``. In particular, the names of the levels of a +``MultiIndex`` can be specified, which is useful if ``reset_index()`` is later +used to move the values from the ``MultiIndex`` to a column. + +.. ipython:: python + + df.rename_axis(index=['abc', 'def']) + +Note that the columns of a ``DataFrame`` are an index, so that using +``rename_axis`` with the ``columns`` argument will change the name of that +index. + +.. ipython:: python + + df.rename_axis(columns="Cols").columns + +Both ``rename`` and ``rename_axis`` support specifying a dictionary, +``Series`` or a mapping function to map labels/names to new values. + +Sorting a ``MultiIndex`` +------------------------ + +For :class:`MultiIndex`-ed objects to be indexed and sliced effectively, +they need to be sorted. As with any index, you can use :meth:`~DataFrame.sort_index`. .. ipython:: python @@ -519,7 +561,7 @@ to be sorted. As with any index, you can use ``sort_index``. .. _advanced.sortlevel_byname: -You may also pass a level name to ``sort_index`` if the MultiIndex levels +You may also pass a level name to ``sort_index`` if the ``MultiIndex`` levels are named. .. ipython:: python @@ -559,14 +601,15 @@ return a copy of the data rather than a view: .. _advanced.unsorted: -Furthermore if you try to index something that is not fully lexsorted, this can raise: +Furthermore, if you try to index something that is not fully lexsorted, this can raise: .. code-block:: ipython In [5]: dfm.loc[(0,'y'):(1, 'z')] UnsortedIndexError: 'Key length (2) was greater than MultiIndex lexsort depth (1)' -The ``is_lexsorted()`` method on an ``Index`` show if the index is sorted, and the ``lexsort_depth`` property returns the sort depth: +The :meth:`~MultiIndex.is_lexsorted` method on a ``MultiIndex`` shows if the +index is sorted, and the ``lexsort_depth`` property returns the sort depth: .. ipython:: python @@ -591,8 +634,8 @@ Take Methods .. _advanced.take: -Similar to NumPy ndarrays, pandas Index, Series, and DataFrame also provides -the ``take`` method that retrieves elements along a given axis at the given +Similar to NumPy ndarrays, pandas ``Index``, ``Series``, and ``DataFrame`` also provides +the :meth:`~DataFrame.take` method that retrieves elements along a given axis at the given indices. The given indices must be either a list or an ndarray of integer index positions. ``take`` will also accept negative integers as relative positions to the end of the object. @@ -657,9 +700,9 @@ faster than fancy indexing. Index Types ----------- -We have discussed ``MultiIndex`` in the previous sections pretty extensively. ``DatetimeIndex`` and ``PeriodIndex`` -are shown :ref:`here `, and information about -`TimedeltaIndex`` is found :ref:`here `. +We have discussed ``MultiIndex`` in the previous sections pretty extensively. +Documentation about ``DatetimeIndex`` and ``PeriodIndex`` are shown :ref:`here `, +and documentation about ``TimedeltaIndex`` is found :ref:`here `. In the following sub-sections we will highlight some other index types. @@ -668,8 +711,8 @@ In the following sub-sections we will highlight some other index types. CategoricalIndex ~~~~~~~~~~~~~~~~ -``CategoricalIndex`` is a type of index that is useful for supporting -indexing with duplicates. This is a container around a ``Categorical`` +:class:`CategoricalIndex` is a type of index that is useful for supporting +indexing with duplicates. This is a container around a :class:`Categorical` and allows efficient indexing and storage of an index with a large number of duplicated elements. .. ipython:: python @@ -758,11 +801,11 @@ Int64Index and RangeIndex Indexing on an integer-based Index with floats has been clarified in 0.18.0, for a summary of the changes, see :ref:`here `. -``Int64Index`` is a fundamental basic index in pandas. -This is an Immutable array implementing an ordered, sliceable set. +:class:`Int64Index` is a fundamental basic index in pandas. +This is an immutable array implementing an ordered, sliceable set. Prior to 0.18.0, the ``Int64Index`` would provide the default index for all ``NDFrame`` objects. -``RangeIndex`` is a sub-class of ``Int64Index`` added in version 0.18.0, now providing the default index for all ``NDFrame`` objects. +:class:`RangeIndex` is a sub-class of ``Int64Index`` added in version 0.18.0, now providing the default index for all ``NDFrame`` objects. ``RangeIndex`` is an optimized version of ``Int64Index`` that can represent a monotonic ordered set. These are analogous to Python `range types `__. .. _indexing.float64index: @@ -770,7 +813,7 @@ Prior to 0.18.0, the ``Int64Index`` would provide the default index for all ``ND Float64Index ~~~~~~~~~~~~ -By default a ``Float64Index`` will be automatically created when passing floating, or mixed-integer-floating values in index creation. +By default a :class:`Float64Index` will be automatically created when passing floating, or mixed-integer-floating values in index creation. This enables a pure label-based slicing paradigm that makes ``[],ix,loc`` for scalar indexing and slicing work exactly the same. @@ -835,8 +878,8 @@ In non-float indexes, slicing using floats will raise a ``TypeError``. Here is a typical use-case for using this type of indexing. Imagine that you have a somewhat -irregular timedelta-like indexing scheme, but the data is recorded as floats. This could for -example be millisecond offsets. +irregular timedelta-like indexing scheme, but the data is recorded as floats. This could, for +example, be millisecond offsets. .. ipython:: python @@ -875,9 +918,9 @@ IntervalIndex .. versionadded:: 0.20.0 -:class:`IntervalIndex` together with its own dtype, ``interval`` as well as the -:class:`Interval` scalar type, allow first-class support in pandas for interval -notation. +:class:`IntervalIndex` together with its own dtype, :class:`~pandas.api.types.IntervalDtype` +as well as the :class:`Interval` scalar type, allow first-class support in pandas +for interval notation. The ``IntervalIndex`` allows some unique indexing and is also used as a return type for the categories in :func:`cut` and :func:`qcut`. @@ -1003,8 +1046,8 @@ Non-monotonic indexes require exact matches If the index of a ``Series`` or ``DataFrame`` is monotonically increasing or decreasing, then the bounds of a label-based slice can be outside the range of the index, much like slice indexing a -normal Python ``list``. Monotonicity of an index can be tested with the ``is_monotonic_increasing`` and -``is_monotonic_decreasing`` attributes. +normal Python ``list``. Monotonicity of an index can be tested with the :meth:`~Index.is_monotonic_increasing` and +:meth:`~Index.is_monotonic_decreasing` attributes. .. ipython:: python @@ -1038,9 +1081,9 @@ On the other hand, if the index is not monotonic, then both slice bounds must be In [11]: df.loc[2:3, :] KeyError: 'Cannot get right slice bound for non-unique label: 3' -:meth:`Index.is_monotonic_increasing` and :meth:`Index.is_monotonic_decreasing` only check that -an index is weakly monotonic. To check for strict montonicity, you can combine one of those with -:meth:`Index.is_unique` +``Index.is_monotonic_increasing`` and ``Index.is_monotonic_decreasing`` only check that +an index is weakly monotonic. To check for strict monotonicity, you can combine one of those with +the :meth:`~Index.is_unique` attribute. .. ipython:: python @@ -1056,7 +1099,7 @@ Compared with standard Python sequence slicing in which the slice endpoint is not inclusive, label-based slicing in pandas **is inclusive**. The primary reason for this is that it is often not possible to easily determine the "successor" or next element after a particular label in an index. For example, -consider the following Series: +consider the following ``Series``: .. ipython:: python diff --git a/doc/source/api.rst b/doc/source/api.rst index 4faec93490fde..665649aead33c 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -61,6 +61,12 @@ Excel read_excel ExcelFile.parse +.. autosummary:: + :toctree: generated/ + :template: autosummary/class_without_autosummary.rst + + ExcelWriter + JSON ~~~~ @@ -100,6 +106,7 @@ HDFStore: PyTables (HDF5) HDFStore.select HDFStore.info HDFStore.keys + HDFStore.walk Feather ~~~~~~~ @@ -238,6 +245,15 @@ Top-level evaluation eval +Hashing +~~~~~~~ + +.. autosummary:: + :toctree: generated/ + + util.hash_array + util.hash_pandas_object + Testing ~~~~~~~ @@ -434,7 +450,6 @@ Computations / Descriptive Stats Series.value_counts Series.compound Series.nonzero - Series.ptp Reindexing / Selection / Label manipulation @@ -444,6 +459,7 @@ Reindexing / Selection / Label manipulation Series.align Series.drop + Series.droplevel Series.drop_duplicates Series.duplicated Series.equals @@ -499,7 +515,6 @@ Reshaping, sorting Series.repeat Series.squeeze Series.view - Series.sortlevel Combining / joining / merging @@ -544,6 +559,7 @@ These can be accessed like ``Series.dt.``. Series.dt.date Series.dt.time + Series.dt.timetz Series.dt.year Series.dt.month Series.dt.day @@ -834,6 +850,22 @@ Sparse SparseSeries.to_coo SparseSeries.from_coo +.. autosummary:: + :toctree: generated/ + :template: autosummary/accessor_attribute.rst + + Series.sparse.npoints + Series.sparse.density + Series.sparse.fill_value + Series.sparse.sp_values + + +.. autosummary:: + :toctree: generated/ + + Series.sparse.from_coo + Series.sparse.to_coo + .. _api.dataframe: DataFrame @@ -898,7 +930,6 @@ Indexing, iteration DataFrame.loc DataFrame.iloc DataFrame.insert - DataFrame.insert DataFrame.__iter__ DataFrame.items DataFrame.keys @@ -1063,6 +1094,7 @@ Reshaping, sorting, transposing .. autosummary:: :toctree: generated/ + DataFrame.droplevel DataFrame.pivot DataFrame.pivot_table DataFrame.reorder_levels @@ -1200,9 +1232,9 @@ Attributes and underlying data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Axes** - * **items**: axis 0; each item corresponds to a DataFrame contained inside - * **major_axis**: axis 1; the index (rows) of each of the DataFrames - * **minor_axis**: axis 2; the columns of each of the DataFrames +* **items**: axis 0; each item corresponds to a DataFrame contained inside +* **major_axis**: axis 1; the index (rows) of each of the DataFrames +* **minor_axis**: axis 2; the columns of each of the DataFrames .. autosummary:: :toctree: generated/ @@ -1641,6 +1673,8 @@ IntervalIndex Components IntervalIndex.is_non_overlapping_monotonic IntervalIndex.get_loc IntervalIndex.get_indexer + IntervalIndex.set_closed + IntervalIndex.overlaps .. _api.multiindex: @@ -1736,6 +1770,7 @@ Time/Date Components DatetimeIndex.nanosecond DatetimeIndex.date DatetimeIndex.time + DatetimeIndex.timetz DatetimeIndex.dayofyear DatetimeIndex.weekofyear DatetimeIndex.week @@ -1869,8 +1904,6 @@ Methods PeriodIndex.asfreq PeriodIndex.strftime PeriodIndex.to_timestamp - PeriodIndex.tz_convert - PeriodIndex.tz_localize Scalars ------- @@ -2028,6 +2061,7 @@ Properties Interval.mid Interval.open_left Interval.open_right + Interval.overlaps Interval.right Timedelta @@ -2070,6 +2104,62 @@ Methods Timedelta.to_timedelta64 Timedelta.total_seconds +.. _api.dateoffsets: + +Date Offsets +------------ + +.. currentmodule:: pandas.tseries.offsets + +.. autosummary:: + :toctree: generated/ + + DateOffset + BusinessDay + BusinessHour + CustomBusinessDay + CustomBusinessHour + MonthOffset + MonthEnd + MonthBegin + BusinessMonthEnd + BusinessMonthBegin + CustomBusinessMonthEnd + CustomBusinessMonthBegin + SemiMonthOffset + SemiMonthEnd + SemiMonthBegin + Week + WeekOfMonth + LastWeekOfMonth + QuarterOffset + BQuarterEnd + BQuarterBegin + QuarterEnd + QuarterBegin + YearOffset + BYearEnd + BYearBegin + YearEnd + YearBegin + FY5253 + FY5253Quarter + Easter + Tick + Day + Hour + Minute + Second + Milli + Micro + Nano + BDay + BMonthEnd + BMonthBegin + CBMonthEnd + CBMonthBegin + CDay + .. _api.frequencies: Frequencies @@ -2349,6 +2439,7 @@ Computations / Descriptive Stats Resampler.std Resampler.sum Resampler.var + Resampler.quantile Style ----- @@ -2555,6 +2646,7 @@ objects. .. autosummary:: :toctree: generated/ + api.extensions.register_extension_dtype api.extensions.register_dataframe_accessor api.extensions.register_series_accessor api.extensions.register_index_accessor @@ -2592,3 +2684,12 @@ objects. generated/pandas.Series.ix generated/pandas.Series.imag generated/pandas.Series.real + + +.. Can't convince sphinx to generate toctree for this class attribute. +.. So we do it manually to avoid a warning + +.. toctree:: + :hidden: + + generated/pandas.api.extensions.ExtensionDtype.na_value diff --git a/doc/source/basics.rst b/doc/source/basics.rst index 8d09f1fc04c1f..81efbfd6d1403 100644 --- a/doc/source/basics.rst +++ b/doc/source/basics.rst @@ -50,9 +50,8 @@ Attributes and the raw ndarray(s) pandas objects have a number of attributes enabling you to access the metadata - * **shape**: gives the axis dimensions of the object, consistent with ndarray - * Axis labels - +* **shape**: gives the axis dimensions of the object, consistent with ndarray +* Axis labels * **Series**: *index* (only axis) * **DataFrame**: *index* (rows) and *columns* * **Panel**: *items*, *major_axis*, and *minor_axis* @@ -131,9 +130,9 @@ Flexible binary operations With binary operations between pandas data structures, there are two key points of interest: - * Broadcasting behavior between higher- (e.g. DataFrame) and - lower-dimensional (e.g. Series) objects. - * Missing data in computations. +* Broadcasting behavior between higher- (e.g. DataFrame) and + lower-dimensional (e.g. Series) objects. +* Missing data in computations. We will demonstrate how to manage these issues independently, though they can be handled simultaneously. @@ -168,7 +167,7 @@ either match on the *index* or *columns* via the **axis** keyword: df_orig = df -Furthermore you can align a level of a multi-indexed DataFrame with a Series. +Furthermore you can align a level of a MultiIndexed DataFrame with a Series. .. ipython:: python @@ -462,10 +461,10 @@ produce an object of the same size. Generally speaking, these methods take an **axis** argument, just like *ndarray.{sum, std, ...}*, but the axis can be specified by name or integer: - - **Series**: no axis argument needed - - **DataFrame**: "index" (axis=0, default), "columns" (axis=1) - - **Panel**: "items" (axis=0), "major" (axis=1, default), "minor" - (axis=2) +* **Series**: no axis argument needed +* **DataFrame**: "index" (axis=0, default), "columns" (axis=1) +* **Panel**: "items" (axis=0), "major" (axis=1, default), "minor" + (axis=2) For example: @@ -593,7 +592,7 @@ categorical columns: frame = pd.DataFrame({'a': ['Yes', 'Yes', 'No', 'No'], 'b': range(4)}) frame.describe() -This behaviour can be controlled by providing a list of types as ``include``/``exclude`` +This behavior can be controlled by providing a list of types as ``include``/``exclude`` arguments. The special value ``all`` can also be used: .. ipython:: python @@ -768,7 +767,7 @@ We encourage you to view the source code of :meth:`~DataFrame.pipe`. .. _dplyr: https://github.com/hadley/dplyr .. _magrittr: https://github.com/smbache/magrittr -.. _R: http://www.r-project.org +.. _R: https://www.r-project.org Row or Column-wise Function Application @@ -1034,7 +1033,7 @@ Passing a single function to ``.transform()`` with a ``Series`` will yield a sin Transform with multiple functions +++++++++++++++++++++++++++++++++ -Passing multiple functions will yield a column multi-indexed DataFrame. +Passing multiple functions will yield a column MultiIndexed DataFrame. The first level will be the original frame column names; the second level will be the names of the transforming functions. @@ -1060,7 +1059,7 @@ Passing a dict of functions will allow selective transforming per column. tsdf.transform({'A': np.abs, 'B': lambda x: x+1}) -Passing a dict of lists will generate a multi-indexed DataFrame with these +Passing a dict of lists will generate a MultiIndexed DataFrame with these selective transforms. .. ipython:: python @@ -1187,11 +1186,11 @@ It is used to implement nearly all other features relying on label-alignment functionality. To *reindex* means to conform the data to match a given set of labels along a particular axis. This accomplishes several things: - * Reorders the existing data to match a new set of labels - * Inserts missing value (NA) markers in label locations where no data for - that label existed - * If specified, **fill** data for missing labels using logic (highly relevant - to working with time series data) +* Reorders the existing data to match a new set of labels +* Inserts missing value (NA) markers in label locations where no data for + that label existed +* If specified, **fill** data for missing labels using logic (highly relevant + to working with time series data) Here is a simple example: @@ -1467,8 +1466,21 @@ for altering the ``Series.name`` attribute. .. _basics.rename_axis: -The Panel class has a related :meth:`~Panel.rename_axis` class which can rename -any of its three axes. +.. versionadded:: 0.24.0 + +The methods :meth:`~DataFrame.rename_axis` and :meth:`~Series.rename_axis` +allow specific names of a `MultiIndex` to be changed (as opposed to the +labels). + +.. ipython:: python + + df = pd.DataFrame({'x': [1, 2, 3, 4, 5, 6], + 'y': [10, 20, 30, 40, 50, 60]}, + index=pd.MultiIndex.from_product([['a', 'b', 'c'], [1, 2]], + names=['let', 'num'])) + df + df.rename_axis(index={'let': 'abc'}) + df.rename_axis(index=str.upper) .. _basics.iteration: @@ -1889,12 +1901,12 @@ faster than sorting the entire Series and calling ``head(n)`` on the result. df.nsmallest(5, ['a', 'c']) -.. _basics.multi-index_sorting: +.. _basics.multiindex_sorting: -Sorting by a multi-index column -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Sorting by a MultiIndex column +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You must be explicit about sorting when the column is a multi-index, and fully specify +You must be explicit about sorting when the column is a MultiIndex, and fully specify all levels to ``by``. .. ipython:: python @@ -1911,10 +1923,10 @@ the axis indexes, since they are immutable) and returns a new object. Note that **it is seldom necessary to copy objects**. For example, there are only a handful of ways to alter a DataFrame *in-place*: - * Inserting, deleting, or modifying a column. - * Assigning to the ``index`` or ``columns`` attributes. - * For homogeneous data, directly modifying the values via the ``values`` - attribute or advanced indexing. +* Inserting, deleting, or modifying a column. +* Assigning to the ``index`` or ``columns`` attributes. +* For homogeneous data, directly modifying the values via the ``values`` + attribute or advanced indexing. To be clear, no pandas method has the side effect of modifying your data; almost every method returns a new object, leaving the original object @@ -1925,11 +1937,24 @@ untouched. If the data is modified, it is because you did so explicitly. dtypes ------ -The main types stored in pandas objects are ``float``, ``int``, ``bool``, -``datetime64[ns]`` and ``datetime64[ns, tz]``, ``timedelta[ns]``, -``category`` and ``object``. In addition these dtypes have item sizes, e.g. -``int64`` and ``int32``. See :ref:`Series with TZ ` -for more detail on ``datetime64[ns, tz]`` dtypes. +For the most part, pandas uses NumPy arrays and dtypes for Series or individual +columns of a DataFrame. The main types allowed in pandas objects are ``float``, +``int``, ``bool``, and ``datetime64[ns]`` (note that NumPy does not support +timezone-aware datetimes). + +In addition to NumPy's types, pandas :ref:`extends ` +NumPy's type-system for a few cases. + +* :ref:`Categorical ` +* :ref:`Datetime with Timezone ` +* :ref:`Period ` +* :ref:`Interval ` + +Pandas uses the ``object`` dtype for storing strings. + +Finally, arbitrary objects may be stored using the ``object`` dtype, but should +be avoided to the extent possible (for performance and interoperability with +other libraries and methods. See :ref:`basics.object_conversion`). A convenient :attr:`~DataFrame.dtypes` attribute for DataFrame returns a Series with the data type of each column. @@ -2112,14 +2137,14 @@ Because the data was transposed the original inference stored all columns as obj The following functions are available for one dimensional object arrays or scalars to perform hard conversion of objects to a specified type: -- :meth:`~pandas.to_numeric` (conversion to numeric dtypes) +* :meth:`~pandas.to_numeric` (conversion to numeric dtypes) .. ipython:: python m = ['1.1', 2, 3] pd.to_numeric(m) -- :meth:`~pandas.to_datetime` (conversion to datetime objects) +* :meth:`~pandas.to_datetime` (conversion to datetime objects) .. ipython:: python @@ -2127,7 +2152,7 @@ hard conversion of objects to a specified type: m = ['2016-07-09', datetime.datetime(2016, 3, 2)] pd.to_datetime(m) -- :meth:`~pandas.to_timedelta` (conversion to timedelta objects) +* :meth:`~pandas.to_timedelta` (conversion to timedelta objects) .. ipython:: python @@ -2271,7 +2296,7 @@ For example, to select ``bool`` columns: df.select_dtypes(include=[bool]) You can also pass the name of a dtype in the `NumPy dtype hierarchy -`__: +`__: .. ipython:: python diff --git a/doc/source/categorical.rst b/doc/source/categorical.rst index e4ce7ebd01dac..acab9de905540 100644 --- a/doc/source/categorical.rst +++ b/doc/source/categorical.rst @@ -358,10 +358,10 @@ Renaming categories is done by assigning new values to the s s.cat.categories = ["Group %s" % g for g in s.cat.categories] s - s.cat.rename_categories([1,2,3]) + s = s.cat.rename_categories([1,2,3]) s # You can also pass a dict-like object to map the renaming - s.cat.rename_categories({1: 'x', 2: 'y', 3: 'z'}) + s = s.cat.rename_categories({1: 'x', 2: 'y', 3: 'z'}) s .. note:: @@ -542,11 +542,11 @@ Comparisons Comparing categorical data with other objects is possible in three cases: - * Comparing equality (``==`` and ``!=``) to a list-like object (list, Series, array, - ...) of the same length as the categorical data. - * All comparisons (``==``, ``!=``, ``>``, ``>=``, ``<``, and ``<=``) of categorical data to - another categorical Series, when ``ordered==True`` and the `categories` are the same. - * All comparisons of a categorical data to a scalar. +* Comparing equality (``==`` and ``!=``) to a list-like object (list, Series, array, + ...) of the same length as the categorical data. +* All comparisons (``==``, ``!=``, ``>``, ``>=``, ``<``, and ``<=``) of categorical data to + another categorical Series, when ``ordered==True`` and the `categories` are the same. +* All comparisons of a categorical data to a scalar. All other comparisons, especially "non-equality" comparisons of two categoricals with different categories or a categorical with any list-like object, will raise a ``TypeError``. diff --git a/doc/source/comparison_with_r.rst b/doc/source/comparison_with_r.rst index a7586f623a160..eecacde8ad14e 100644 --- a/doc/source/comparison_with_r.rst +++ b/doc/source/comparison_with_r.rst @@ -18,11 +18,11 @@ was started to provide a more detailed look at the `R language party libraries as they relate to ``pandas``. In comparisons with R and CRAN libraries, we care about the following things: - - **Functionality / flexibility**: what can/cannot be done with each tool - - **Performance**: how fast are operations. Hard numbers/benchmarks are - preferable - - **Ease-of-use**: Is one tool easier/harder to use (you may have to be - the judge of this, given side-by-side code comparisons) +* **Functionality / flexibility**: what can/cannot be done with each tool +* **Performance**: how fast are operations. Hard numbers/benchmarks are + preferable +* **Ease-of-use**: Is one tool easier/harder to use (you may have to be + the judge of this, given side-by-side code comparisons) This page is also here to offer a bit of a translation guide for users of these R packages. diff --git a/doc/source/comparison_with_sas.rst b/doc/source/comparison_with_sas.rst index 0354ad473544b..4d7acdf9ab16c 100644 --- a/doc/source/comparison_with_sas.rst +++ b/doc/source/comparison_with_sas.rst @@ -365,8 +365,8 @@ Length ~~~~~~ SAS determines the length of a character string with the -`LENGTHN `__ -and `LENGTHC `__ +`LENGTHN `__ +and `LENGTHC `__ functions. ``LENGTHN`` excludes trailing blanks and ``LENGTHC`` includes trailing blanks. .. code-block:: sas @@ -391,7 +391,7 @@ Find ~~~~ SAS determines the position of a character in a string with the -`FINDW `__ function. +`FINDW `__ function. ``FINDW`` takes the string defined by the first argument and searches for the first position of the substring you supply as the second argument. @@ -417,7 +417,7 @@ Substring ~~~~~~~~~ SAS extracts a substring from a string based on its position with the -`SUBSTR `__ function. +`SUBSTR `__ function. .. code-block:: sas @@ -438,7 +438,7 @@ indexes are zero-based. Scan ~~~~ -The SAS `SCAN `__ +The SAS `SCAN `__ function returns the nth word from a string. The first argument is the string you want to parse and the second argument specifies which word you want to extract. @@ -469,9 +469,9 @@ approaches, but this just shows a simple approach. Upcase, Lowcase, and Propcase ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The SAS `UPCASE `__ -`LOWCASE `__ and -`PROPCASE `__ +The SAS `UPCASE `__ +`LOWCASE `__ and +`PROPCASE `__ functions change the case of the argument. .. code-block:: sas @@ -709,7 +709,7 @@ This means that the size of data able to be loaded in pandas is limited by your machine's memory, but also that the operations on that data may be faster. If out of core processing is needed, one possibility is the -`dask.dataframe `_ +`dask.dataframe `_ library (currently in development) which provides a subset of pandas functionality for an on-disk ``DataFrame`` diff --git a/doc/source/comparison_with_sql.rst b/doc/source/comparison_with_sql.rst index ba069b5a44c72..db143cd586441 100644 --- a/doc/source/comparison_with_sql.rst +++ b/doc/source/comparison_with_sql.rst @@ -4,7 +4,7 @@ Comparison with SQL ******************** Since many potential pandas users have some familiarity with -`SQL `_, this page is meant to provide some examples of how +`SQL `_, this page is meant to provide some examples of how various SQL operations would be performed using pandas. If you're new to pandas, you might want to first read through :ref:`10 Minutes to pandas<10min>` @@ -59,7 +59,7 @@ Filtering in SQL is done via a WHERE clause. LIMIT 5; DataFrames can be filtered in multiple ways; the most intuitive of which is using -`boolean indexing `_. +`boolean indexing `_. .. ipython:: python diff --git a/doc/source/computation.rst b/doc/source/computation.rst index ff06c369e1897..0d2021de8f88e 100644 --- a/doc/source/computation.rst +++ b/doc/source/computation.rst @@ -153,6 +153,21 @@ Like ``cov``, ``corr`` also supports the optional ``min_periods`` keyword: frame.corr(min_periods=12) +.. versionadded:: 0.24.0 + +The ``method`` argument can also be a callable for a generic correlation +calculation. In this case, it should be a single function +that produces a single value from two ndarray inputs. Suppose we wanted to +compute the correlation based on histogram intersection: + +.. ipython:: python + + # histogram intersection + histogram_intersection = lambda a, b: np.minimum( + np.true_divide(a, a.sum()), np.true_divide(b, b.sum()) + ).sum() + frame.corr(method=histogram_intersection) + A related method :meth:`~DataFrame.corrwith` is implemented on DataFrame to compute the correlation between like-labeled Series contained in different DataFrame objects. @@ -344,20 +359,20 @@ The weights used in the window are specified by the ``win_type`` keyword. The list of recognized types are the `scipy.signal window functions `__: -- ``boxcar`` -- ``triang`` -- ``blackman`` -- ``hamming`` -- ``bartlett`` -- ``parzen`` -- ``bohman`` -- ``blackmanharris`` -- ``nuttall`` -- ``barthann`` -- ``kaiser`` (needs beta) -- ``gaussian`` (needs std) -- ``general_gaussian`` (needs power, width) -- ``slepian`` (needs width). +* ``boxcar`` +* ``triang`` +* ``blackman`` +* ``hamming`` +* ``bartlett`` +* ``parzen`` +* ``bohman`` +* ``blackmanharris`` +* ``nuttall`` +* ``barthann`` +* ``kaiser`` (needs beta) +* ``gaussian`` (needs std) +* ``general_gaussian`` (needs power, width) +* ``slepian`` (needs width). .. ipython:: python @@ -537,10 +552,10 @@ Binary Window Functions two ``Series`` or any combination of ``DataFrame/Series`` or ``DataFrame/DataFrame``. Here is the behavior in each case: -- two ``Series``: compute the statistic for the pairing. -- ``DataFrame/Series``: compute the statistics for each column of the DataFrame +* two ``Series``: compute the statistic for the pairing. +* ``DataFrame/Series``: compute the statistics for each column of the DataFrame with the passed Series, thus returning a DataFrame. -- ``DataFrame/DataFrame``: by default compute the statistic for matching column +* ``DataFrame/DataFrame``: by default compute the statistic for matching column names, returning a DataFrame. If the keyword argument ``pairwise=True`` is passed then computes the statistic for each pair of columns, returning a ``MultiIndexed DataFrame`` whose ``index`` are the dates in question (see :ref:`the next section @@ -741,10 +756,10 @@ Aside from not having a ``window`` parameter, these functions have the same interfaces as their ``.rolling`` counterparts. Like above, the parameters they all accept are: -- ``min_periods``: threshold of non-null data points to require. Defaults to +* ``min_periods``: threshold of non-null data points to require. Defaults to minimum needed to compute statistic. No ``NaNs`` will be output once ``min_periods`` non-null data points have been seen. -- ``center``: boolean, whether to set the labels at the center (default is False). +* ``center``: boolean, whether to set the labels at the center (default is False). .. _stats.moments.expanding.note: .. note:: @@ -903,12 +918,12 @@ of an EW moment: One must specify precisely one of **span**, **center of mass**, **half-life** and **alpha** to the EW functions: -- **Span** corresponds to what is commonly called an "N-day EW moving average". -- **Center of mass** has a more physical interpretation and can be thought of +* **Span** corresponds to what is commonly called an "N-day EW moving average". +* **Center of mass** has a more physical interpretation and can be thought of in terms of span: :math:`c = (s - 1) / 2`. -- **Half-life** is the period of time for the exponential weight to reduce to +* **Half-life** is the period of time for the exponential weight to reduce to one half. -- **Alpha** specifies the smoothing factor directly. +* **Alpha** specifies the smoothing factor directly. Here is an example for a univariate time series: diff --git a/doc/source/conf.py b/doc/source/conf.py index d516e67b947ba..b0501eaf54dc2 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -16,9 +16,11 @@ import re import inspect import importlib -from sphinx.ext.autosummary import _import_by_name +import logging import warnings +from sphinx.ext.autosummary import _import_by_name +logger = logging.getLogger(__name__) try: raw_input # Python 2 @@ -75,8 +77,19 @@ 'nbsphinx', ] +try: + import sphinxcontrib.spelling # noqa +except ImportError as err: + logger.warn(('sphinxcontrib.spelling failed to import with error "{}". ' + '`spellcheck` command is not available.'.format(err))) +else: + extensions.append('sphinxcontrib.spelling') + exclude_patterns = ['**.ipynb_checkpoints'] +spelling_word_list_filename = ['spelling_wordlist.txt', 'names_wordlist.txt'] +spelling_ignore_pypi_package_names = True + with open("index.rst") as f: index_rst_lines = f.readlines() @@ -86,7 +99,7 @@ # JP: added from sphinxdocs autosummary_generate = False -if any(re.match("\s*api\s*", l) for l in index_rst_lines): +if any(re.match(r"\s*api\s*", l) for l in index_rst_lines): autosummary_generate = True # numpydoc @@ -200,16 +213,16 @@ # of the sidebar. # html_logo = None -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +html_favicon = os.path.join(html_static_path[0], 'favicon.ico') + # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' @@ -328,8 +341,8 @@ # file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'pandas.tex', - u'pandas: powerful Python data analysis toolkit', - u'Wes McKinney\n\& PyData Development Team', 'manual'), + 'pandas: powerful Python data analysis toolkit', + r'Wes McKinney\n\& PyData Development Team', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -556,7 +569,11 @@ def linkcode_resolve(domain, info): return None try: - fn = inspect.getsourcefile(obj) + # inspect.unwrap() was added in Python version 3.4 + if sys.version_info >= (3, 5): + fn = inspect.getsourcefile(inspect.unwrap(obj)) + else: + fn = inspect.getsourcefile(obj) except: fn = None if not fn: diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst index e9939250052f1..3ec505998fde0 100644 --- a/doc/source/contributing.rst +++ b/doc/source/contributing.rst @@ -138,11 +138,11 @@ steps; you only need to install the compiler. For Windows developers, the following links may be helpful. -- https://blogs.msdn.microsoft.com/pythonengineering/2016/04/11/unable-to-find-vcvarsall-bat/ -- https://github.com/conda/conda-recipes/wiki/Building-from-Source-on-Windows-32-bit-and-64-bit -- https://cowboyprogrammer.org/building-python-wheels-for-windows/ -- https://blog.ionelmc.ro/2014/12/21/compiling-python-extensions-on-windows/ -- https://support.enthought.com/hc/en-us/articles/204469260-Building-Python-extensions-with-Canopy +* https://blogs.msdn.microsoft.com/pythonengineering/2016/04/11/unable-to-find-vcvarsall-bat/ +* https://github.com/conda/conda-recipes/wiki/Building-from-Source-on-Windows-32-bit-and-64-bit +* https://cowboyprogrammer.org/building-python-wheels-for-windows/ +* https://blog.ionelmc.ro/2014/12/21/compiling-python-extensions-on-windows/ +* https://support.enthought.com/hc/en-us/articles/204469260-Building-Python-extensions-with-Canopy Let us know if you have any difficulties by opening an issue or reaching out on `Gitter`_. @@ -155,11 +155,11 @@ Creating a Python Environment Now that you have a C compiler, create an isolated pandas development environment: -- Install either `Anaconda `_ or `miniconda +* Install either `Anaconda `_ or `miniconda `_ -- Make sure your conda is up to date (``conda update conda``) -- Make sure that you have :ref:`cloned the repository ` -- ``cd`` to the *pandas* source directory +* Make sure your conda is up to date (``conda update conda``) +* Make sure that you have :ref:`cloned the repository ` +* ``cd`` to the *pandas* source directory We'll now kick off a three-step process: @@ -286,7 +286,7 @@ complex changes to the documentation as well. Some other important things to know about the docs: -- The *pandas* documentation consists of two parts: the docstrings in the code +* The *pandas* documentation consists of two parts: the docstrings in the code itself and the docs in this folder ``pandas/doc/``. The docstrings provide a clear explanation of the usage of the individual @@ -294,7 +294,7 @@ Some other important things to know about the docs: overviews per topic together with some other information (what's new, installation, etc). -- The docstrings follow a pandas convention, based on the **Numpy Docstring +* The docstrings follow a pandas convention, based on the **Numpy Docstring Standard**. Follow the :ref:`pandas docstring guide ` for detailed instructions on how to write a correct docstring. @@ -303,7 +303,7 @@ Some other important things to know about the docs: contributing_docstring.rst -- The tutorials make heavy use of the `ipython directive +* The tutorials make heavy use of the `ipython directive `_ sphinx extension. This directive lets you put code in the documentation which will be run during the doc build. For example:: @@ -324,7 +324,7 @@ Some other important things to know about the docs: doc build. This approach means that code examples will always be up to date, but it does make the doc building a bit more complex. -- Our API documentation in ``doc/source/api.rst`` houses the auto-generated +* Our API documentation in ``doc/source/api.rst`` houses the auto-generated documentation from the docstrings. For classes, there are a few subtleties around controlling which methods and attributes have pages auto-generated. @@ -365,6 +365,31 @@ This will identify methods documented in ``doc/source/api.rst`` that are not act class methods, and existing methods that are not documented in ``doc/source/api.rst``. +Updating a *pandas* docstring +----------------------------- + +When improving a single function or method's docstring, it is not necessarily +needed to build the full documentation (see next section). +However, there is a script that checks a docstring (for example for the ``DataFrame.mean`` method):: + + python scripts/validate_docstrings.py pandas.DataFrame.mean + +This script will indicate some formatting errors if present, and will also +run and test the examples included in the docstring. +Check the :ref:`pandas docstring guide ` for a detailed guide +on how to format the docstring. + +The examples in the docstring ('doctests') must be valid Python code, +that in a deterministic way returns the presented output, and that can be +copied and run by users. This can be checked with the script above, and is +also tested on Travis. A failing doctest will be a blocker for merging a PR. +Check the :ref:`examples ` section in the docstring guide +for some tips and tricks to get the doctests passing. + +When doing a PR with a docstring update, it is good to post the +output of the validation script in a comment on github. + + How to build the *pandas* documentation --------------------------------------- @@ -436,6 +461,25 @@ the documentation are also built by Travis-CI. These docs are then hosted `here `__, see also the :ref:`Continuous Integration ` section. +Spell checking documentation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When contributing to documentation to **pandas** it's good to check if your work +contains any spelling errors. Sphinx provides an easy way to spell check documentation +and docstrings. + +Running the spell check is easy. Just navigate to your local ``pandas/doc/`` directory and run:: + + python make.py spellcheck + +The spellcheck will take a few minutes to run (between 1 to 6 minutes). Sphinx will alert you +with warnings and misspelt words - these misspelt words will be added to a file called +``output.txt`` and you can find it on your local directory ``pandas/doc/build/spelling/``. + +The Sphinx spelling extension uses an EN-US dictionary to correct words, what means that in +some cases you might need to add a word to this dictionary. You can do so by adding the word to +the bag-of-words file named ``spelling_wordlist.txt`` located in the folder ``pandas/doc/``. + .. _contributing.code: Contributing to the code base @@ -453,6 +497,17 @@ tools will be run to check your code for stylistic errors. Generating any warnings will cause the test to fail. Thus, good style is a requirement for submitting code to *pandas*. +There is a tool in pandas to help contributors verify their changes before +contributing them to the project:: + + ./ci/code_checks.sh + +The script verify the linting of code files, it looks for common mistake patterns +(like missing spaces around sphinx directives that make the documentation not +being rendered properly) and it also validates the doctests. It is possible to +run the checks independently by using the parameters ``lint``, ``patterns`` and +``doctests`` (e.g. ``./ci/code_checks.sh lint``). + In addition, because a lot of people use our library, it is important that we do not make sudden changes to the code that could have the potential to break a lot of user code as a result, that is, we need it to be as *backwards compatible* @@ -469,8 +524,8 @@ standard. Google provides an open source style checker called ``cpplint``, but w use a fork of it that can be found `here `__. Here are *some* of the more common ``cpplint`` issues: - - we restrict line-length to 80 characters to promote readability - - every header file must include a header guard to avoid name collisions if re-included +* we restrict line-length to 80 characters to promote readability +* every header file must include a header guard to avoid name collisions if re-included :ref:`Continuous Integration ` will run the `cpplint `_ tool @@ -517,8 +572,8 @@ Python (PEP8) There are several tools to ensure you abide by this standard. Here are *some* of the more common ``PEP8`` issues: - - we restrict line-length to 79 characters to promote readability - - passing arguments should have spaces after commas, e.g. ``foo(arg1, arg2, kw1='bar')`` +* we restrict line-length to 79 characters to promote readability +* passing arguments should have spaces after commas, e.g. ``foo(arg1, arg2, kw1='bar')`` :ref:`Continuous Integration ` will run the `flake8 `_ tool @@ -557,6 +612,54 @@ Alternatively, you can install the ``grep`` and ``xargs`` commands via the `MinGW `__ toolchain, and it will allow you to run the commands above. +.. _contributing.import-formatting: + +Import Formatting +~~~~~~~~~~~~~~~~~ +*pandas* uses `isort `__ to standardise import +formatting across the codebase. + +A guide to import layout as per pep8 can be found `here `__. + +A summary of our current import sections ( in order ): + +* Future +* Python Standard Library +* Third Party +* ``pandas._libs``, ``pandas.compat``, ``pandas.util._*``, ``pandas.errors`` (largely not dependent on ``pandas.core``) +* ``pandas.core.dtypes`` (largely not dependent on the rest of ``pandas.core``) +* Rest of ``pandas.core.*`` +* Non-core ``pandas.io``, ``pandas.plotting``, ``pandas.tseries`` +* Local application/library specific imports + +Imports are alphabetically sorted within these sections. + + +As part of :ref:`Continuous Integration ` checks we run:: + + isort --recursive --check-only pandas + +to check that imports are correctly formatted as per the `setup.cfg`. + +If you see output like the below in :ref:`Continuous Integration ` checks: + +.. code-block:: shell + + Check import format using isort + ERROR: /home/travis/build/pandas-dev/pandas/pandas/io/pytables.py Imports are incorrectly sorted + Check import format using isort DONE + The command "ci/code_checks.sh" exited with 1 + +You should run:: + + isort pandas/io/pytables.py + +to automatically format imports correctly. This will modify your local copy of the files. + +The `--recursive` flag can be passed to sort all files in a directory. + +You can then verify the changes look ok, then git :ref:`commit ` and :ref:`push `. + Backwards Compatibility ~~~~~~~~~~~~~~~~~~~~~~~ @@ -588,18 +691,26 @@ Otherwise, you need to do it manually: warnings.warn('Use new_func instead.', FutureWarning, stacklevel=2) new_func() +You'll also need to + +1. write a new test that asserts a warning is issued when calling with the deprecated argument +2. Update all of pandas existing tests and code to use the new argument + +See :ref:`contributing.warnings` for more. + + .. _contributing.ci: Testing With Continuous Integration ----------------------------------- The *pandas* test suite will run automatically on `Travis-CI `__, -`Appveyor `__, and `Circle CI `__ continuous integration -services, once your pull request is submitted. +`Azure Pipelines `__, +and `Circle CI `__ continuous integration services, once your pull request is submitted. However, if you wish to run the test suite on a branch prior to submitting the pull request, then the continuous integration services need to be hooked to your GitHub repository. Instructions are here for `Travis-CI `__, -`Appveyor `__ , and `CircleCI `__. +`Azure Pipelines `__, and `CircleCI `__. A pull-request will be considered for merging when you have an all 'green' build. If any tests are failing, then you will get a red 'X', where you can click through to see the individual failed tests. @@ -609,8 +720,8 @@ This is an example of a green build. .. note:: - Each time you push to *your* fork, a *new* run of the tests will be triggered on the CI. Appveyor will auto-cancel - any non-currently-running tests for that same pull-request. You can enable the auto-cancel feature for + Each time you push to *your* fork, a *new* run of the tests will be triggered on the CI. + You can enable the auto-cancel feature, which removes any non-currently-running tests for that same pull-request, for `Travis-CI here `__ and for `CircleCI here `__. @@ -621,7 +732,7 @@ Test-driven development/code writing ------------------------------------ *pandas* is serious about testing and strongly encourages contributors to embrace -`test-driven development (TDD) `_. +`test-driven development (TDD) `_. This development process "relies on the repetition of a very short development cycle: first the developer writes an (initially failing) automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that test." @@ -633,13 +744,13 @@ Adding tests is one of the most common requests after code is pushed to *pandas* it is worth getting in the habit of writing tests ahead of time so this is never an issue. Like many packages, *pandas* uses `pytest -`_ and the convenient +`_ and the convenient extensions in `numpy.testing `_. .. note:: - The earliest supported pytest version is 3.1.0. + The earliest supported pytest version is 3.6.0. Writing tests ~~~~~~~~~~~~~ @@ -683,7 +794,7 @@ Transitioning to ``pytest`` class TestReallyCoolFeature(object): .... -Going forward, we are moving to a more *functional* style using the `pytest `__ framework, which offers a richer testing +Going forward, we are moving to a more *functional* style using the `pytest `__ framework, which offers a richer testing framework that will facilitate testing and developing. Thus, instead of writing test classes, we will write test functions like this: .. code-block:: python @@ -696,14 +807,14 @@ Using ``pytest`` Here is an example of a self-contained set of tests that illustrate multiple features that we like to use. -- functional style: tests are like ``test_*`` and *only* take arguments that are either fixtures or parameters -- ``pytest.mark`` can be used to set metadata on test functions, e.g. ``skip`` or ``xfail``. -- using ``parametrize``: allow testing of multiple cases -- to set a mark on a parameter, ``pytest.param(..., marks=...)`` syntax should be used -- ``fixture``, code for object construction, on a per-test basis -- using bare ``assert`` for scalars and truth-testing -- ``tm.assert_series_equal`` (and its counter part ``tm.assert_frame_equal``), for pandas object comparisons. -- the typical pattern of constructing an ``expected`` and comparing versus the ``result`` +* functional style: tests are like ``test_*`` and *only* take arguments that are either fixtures or parameters +* ``pytest.mark`` can be used to set metadata on test functions, e.g. ``skip`` or ``xfail``. +* using ``parametrize``: allow testing of multiple cases +* to set a mark on a parameter, ``pytest.param(..., marks=...)`` syntax should be used +* ``fixture``, code for object construction, on a per-test basis +* using bare ``assert`` for scalars and truth-testing +* ``tm.assert_series_equal`` (and its counter part ``tm.assert_frame_equal``), for pandas object comparisons. +* the typical pattern of constructing an ``expected`` and comparing versus the ``result`` We would name this file ``test_cool_feature.py`` and put in an appropriate place in the ``pandas/tests/`` structure. @@ -747,7 +858,7 @@ A test run of this yields ((pandas) bash-3.2$ pytest test_cool_feature.py -v =========================== test session starts =========================== - platform darwin -- Python 3.6.2, pytest-3.2.1, py-1.4.31, pluggy-0.4.0 + platform darwin -- Python 3.6.2, pytest-3.6.0, py-1.4.31, pluggy-0.4.0 collected 11 items tester.py::test_dtypes[int8] PASSED @@ -769,13 +880,102 @@ Tests that we have ``parametrized`` are now accessible via the test name, for ex ((pandas) bash-3.2$ pytest test_cool_feature.py -v -k int8 =========================== test session starts =========================== - platform darwin -- Python 3.6.2, pytest-3.2.1, py-1.4.31, pluggy-0.4.0 + platform darwin -- Python 3.6.2, pytest-3.6.0, py-1.4.31, pluggy-0.4.0 collected 11 items test_cool_feature.py::test_dtypes[int8] PASSED test_cool_feature.py::test_series[int8] PASSED +.. _using-hypothesis: + +Using ``hypothesis`` +~~~~~~~~~~~~~~~~~~~~ + +Hypothesis is a library for property-based testing. Instead of explicitly +parametrizing a test, you can describe *all* valid inputs and let Hypothesis +try to find a failing input. Even better, no matter how many random examples +it tries, Hypothesis always reports a single minimal counterexample to your +assertions - often an example that you would never have thought to test. + +See `Getting Started with Hypothesis `_ +for more of an introduction, then `refer to the Hypothesis documentation +for details `_. + +.. code-block:: python + + import json + from hypothesis import given, strategies as st + + any_json_value = st.deferred(lambda: st.one_of( + st.none(), st.booleans(), st.floats(allow_nan=False), st.text(), + st.lists(any_json_value), st.dictionaries(st.text(), any_json_value) + )) + + @given(value=any_json_value) + def test_json_roundtrip(value): + result = json.loads(json.dumps(value)) + assert value == result + +This test shows off several useful features of Hypothesis, as well as +demonstrating a good use-case: checking properties that should hold over +a large or complicated domain of inputs. + +To keep the Pandas test suite running quickly, parametrized tests are +preferred if the inputs or logic are simple, with Hypothesis tests reserved +for cases with complex logic or where there are too many combinations of +options or subtle interactions to test (or think of!) all of them. + +.. _contributing.warnings: + +Testing Warnings +~~~~~~~~~~~~~~~~ + +By default, one of pandas CI workers will fail if any unhandled warnings are emitted. + +If your change involves checking that a warning is actually emitted, use +``tm.assert_produces_warning(ExpectedWarning)``. + + +.. code-block:: python + + with tm.assert_produces_warning(FutureWarning): + df.some_operation() + +We prefer this to the ``pytest.warns`` context manager because ours checks that the warning's +stacklevel is set correctly. The stacklevel is what ensure the *user's* file name and line number +is printed in the warning, rather than something internal to pandas. It represents the number of +function calls from user code (e.g. ``df.some_operation()``) to the function that actually emits +the warning. Our linter will fail the build if you use ``pytest.warns`` in a test. + +If you have a test that would emit a warning, but you aren't actually testing the +warning itself (say because it's going to be removed in the future, or because we're +matching a 3rd-party library's behavior), then use ``pytest.mark.filterwarnings`` to +ignore the error. + +.. code-block:: python + + @pytest.mark.filterwarnings("ignore:msg:category") + def test_thing(self): + ... + +If the test generates a warning of class ``category`` whose message starts +with ``msg``, the warning will be ignored and the test will pass. + +If you need finer-grained control, you can use Python's usual +`warnings module `__ +to control whether a warning is ignored / raised at different places within +a single test. + +.. code-block:: python + + with warch.catch_warnings(): + warnings.simplefilter("ignore", FutureWarning) + # Or use warnings.filterwarnings(...) + +Alternatively, consider breaking up the unit test. + + Running the test suite ---------------------- @@ -818,7 +1018,7 @@ On Windows, one can type:: This can significantly reduce the time it takes to locally run tests before submitting a pull request. -For more, see the `pytest `_ documentation. +For more, see the `pytest `_ documentation. .. versionadded:: 0.20.0 @@ -926,6 +1126,8 @@ or a new keyword argument (`example `_ serves +automatically from docstrings. `Sphinx `_ serves this purpose. Next example gives an idea on how a docstring looks like: @@ -68,7 +68,7 @@ As PEP-257 is quite open, and some other standards exist on top of it. In the case of pandas, the numpy docstring convention is followed. The conventions is explained in this document: -- `numpydoc docstring guide `_ +* `numpydoc docstring guide `_ (which is based in the original `Guide to NumPy/SciPy documentation `_) @@ -78,9 +78,9 @@ The standard uses reStructuredText (reST). reStructuredText is a markup language that allows encoding styles in plain text files. Documentation about reStructuredText can be found in: -- `Sphinx reStructuredText primer `_ -- `Quick reStructuredText reference `_ -- `Full reStructuredText specification `_ +* `Sphinx reStructuredText primer `_ +* `Quick reStructuredText reference `_ +* `Full reStructuredText specification `_ Pandas has some helpers for sharing docstrings between related classes, see :ref:`docstring.sharing`. @@ -103,23 +103,23 @@ left before or after the docstring. The text starts in the next line after the opening quotes. The closing quotes have their own line (meaning that they are not at the end of the last sentence). -In rare occasions reST styles like bold text or itallics will be used in +In rare occasions reST styles like bold text or italics will be used in docstrings, but is it common to have inline code, which is presented between backticks. It is considered inline code: -- The name of a parameter -- Python code, a module, function, built-in, type, literal... (e.g. ``os``, +* The name of a parameter +* Python code, a module, function, built-in, type, literal... (e.g. ``os``, ``list``, ``numpy.abs``, ``datetime.date``, ``True``) -- A pandas class (in the form ``:class:`pandas.Series```) -- A pandas method (in the form ``:meth:`pandas.Series.sum```) -- A pandas function (in the form ``:func:`pandas.to_datetime```) +* A pandas class (in the form ``:class:`pandas.Series```) +* A pandas method (in the form ``:meth:`pandas.Series.sum```) +* A pandas function (in the form ``:func:`pandas.to_datetime```) .. note:: To display only the last component of the linked class, method or function, prefix it with ``~``. For example, ``:class:`~pandas.Series``` will link to ``pandas.Series`` but only display the last part, ``Series`` as the link text. See `Sphinx cross-referencing syntax - `_ + `_ for details. **Good:** @@ -243,7 +243,7 @@ their use cases, if it is not too generic. """ Pivot a row index to columns. - When using a multi-index, a level can be pivoted so each value in + When using a MultiIndex, a level can be pivoted so each value in the index becomes a column. This is especially useful when a subindex is repeated for the main index, and data is easier to visualize as a pivot table. @@ -352,71 +352,71 @@ When specifying the parameter types, Python built-in data types can be used directly (the Python type is preferred to the more verbose string, integer, boolean, etc): -- int -- float -- str -- bool +* int +* float +* str +* bool For complex types, define the subtypes. For `dict` and `tuple`, as more than one type is present, we use the brackets to help read the type (curly brackets for `dict` and normal brackets for `tuple`): -- list of int -- dict of {str : int} -- tuple of (str, int, int) -- tuple of (str,) -- set of str +* list of int +* dict of {str : int} +* tuple of (str, int, int) +* tuple of (str,) +* set of str In case where there are just a set of values allowed, list them in curly brackets and separated by commas (followed by a space). If the values are ordinal and they have an order, list them in this order. Otherwise, list the default value first, if there is one: -- {0, 10, 25} -- {'simple', 'advanced'} -- {'low', 'medium', 'high'} -- {'cat', 'dog', 'bird'} +* {0, 10, 25} +* {'simple', 'advanced'} +* {'low', 'medium', 'high'} +* {'cat', 'dog', 'bird'} If the type is defined in a Python module, the module must be specified: -- datetime.date -- datetime.datetime -- decimal.Decimal +* datetime.date +* datetime.datetime +* decimal.Decimal If the type is in a package, the module must be also specified: -- numpy.ndarray -- scipy.sparse.coo_matrix +* numpy.ndarray +* scipy.sparse.coo_matrix If the type is a pandas type, also specify pandas except for Series and DataFrame: -- Series -- DataFrame -- pandas.Index -- pandas.Categorical -- pandas.SparseArray +* Series +* DataFrame +* pandas.Index +* pandas.Categorical +* pandas.SparseArray If the exact type is not relevant, but must be compatible with a numpy array, array-like can be specified. If Any type that can be iterated is accepted, iterable can be used: -- array-like -- iterable +* array-like +* iterable If more than one type is accepted, separate them by commas, except the last two types, that need to be separated by the word 'or': -- int or float -- float, decimal.Decimal or None -- str or list of str +* int or float +* float, decimal.Decimal or None +* str or list of str If ``None`` is one of the accepted values, it always needs to be the last in the list. For axis, the convention is to use something like: -- axis : {0 or 'index', 1 or 'columns', None}, default None +* axis : {0 or 'index', 1 or 'columns', None}, default None .. _docstring.returns: @@ -706,7 +706,7 @@ than 5, to show the example with the default values. If doing the ``mean``, we could use something like ``[1, 2, 3]``, so it is easy to see that the value returned is the mean. -For more complex examples (groupping for example), avoid using data without +For more complex examples (grouping for example), avoid using data without interpretation, like a matrix of random numbers with columns A, B, C, D... And instead use a meaningful example, which makes it easier to understand the concept. Unless required by the example, use names of animals, to keep examples @@ -877,7 +877,7 @@ be tricky. Here are some attention points: the actual error only the error name is sufficient. * If there is a small part of the result that can vary (e.g. a hash in an object - represenation), you can use ``...`` to represent this part. + representation), you can use ``...`` to represent this part. If you want to show that ``s.plot()`` returns a matplotlib AxesSubplot object, this will fail the doctest :: diff --git a/doc/source/cookbook.rst b/doc/source/cookbook.rst index 893642410af02..3d26a9c7d3d54 100644 --- a/doc/source/cookbook.rst +++ b/doc/source/cookbook.rst @@ -52,7 +52,7 @@ Idioms These are some neat pandas ``idioms`` `if-then/if-then-else on one column, and assignment to another one or more columns: -`__ +`__ .. ipython:: python @@ -88,7 +88,7 @@ Or use pandas where after you've set up a mask df.where(df_mask,-1000) `if-then-else using numpy's where() -`__ +`__ .. ipython:: python @@ -101,7 +101,7 @@ Splitting ********* `Split a frame with a boolean criterion -`__ +`__ .. ipython:: python @@ -115,7 +115,7 @@ Building Criteria ***************** `Select with multi-column criteria -`__ +`__ .. ipython:: python @@ -132,7 +132,7 @@ Building Criteria .. ipython:: python - newseries = df.loc[(df['BBB'] > 25) | (df['CCC'] >= -40), 'AAA']; newseries; + newseries = df.loc[(df['BBB'] > 25) | (df['CCC'] >= -40), 'AAA']; newseries ...or (with assignment modifies the DataFrame.) @@ -141,7 +141,7 @@ Building Criteria df.loc[(df['BBB'] > 25) | (df['CCC'] >= 75), 'AAA'] = 0.1; df `Select rows with data closest to certain value using argsort -`__ +`__ .. ipython:: python @@ -152,7 +152,7 @@ Building Criteria df.loc[(df.CCC-aValue).abs().argsort()] `Dynamically reduce a list of criteria using a binary operators -`__ +`__ .. ipython:: python @@ -189,7 +189,7 @@ DataFrames The :ref:`indexing ` docs. `Using both row labels and value conditionals -`__ +`__ .. ipython:: python @@ -232,7 +232,7 @@ Ambiguity arises when an index consists of integers with a non-zero start or non df2.loc[1:3] #Label-oriented `Using inverse operator (~) to take the complement of a mask -`__ +`__ .. ipython:: python @@ -259,13 +259,13 @@ Panels pf.loc[:,:,'F'] = pd.DataFrame(data, rng, cols);pf `Mask a panel by using np.where and then reconstructing the panel with the new masked values -`__ +`__ New Columns *********** `Efficiently and dynamically creating new columns using applymap -`__ +`__ .. ipython:: python @@ -279,14 +279,14 @@ New Columns df[new_cols] = df[source_cols].applymap(categories.get);df `Keep other columns when using min() with groupby -`__ +`__ .. ipython:: python df = pd.DataFrame( {'AAA' : [1,1,1,2,2,2,3,3], 'BBB' : [2,1,3,4,5,1,2,3]}); df -Method 1 : idxmin() to get the index of the mins +Method 1 : idxmin() to get the index of the minimums .. ipython:: python @@ -307,8 +307,8 @@ MultiIndexing The :ref:`multindexing ` docs. -`Creating a multi-index from a labeled frame -`__ +`Creating a MultiIndex from a labeled frame +`__ .. ipython:: python @@ -330,8 +330,8 @@ The :ref:`multindexing ` docs. Arithmetic ********** -`Performing arithmetic with a multi-index that needs broadcasting -`__ +`Performing arithmetic with a MultiIndex that needs broadcasting +`__ .. ipython:: python @@ -342,8 +342,8 @@ Arithmetic Slicing ******* -`Slicing a multi-index with xs -`__ +`Slicing a MultiIndex with xs +`__ .. ipython:: python @@ -363,8 +363,8 @@ To take the cross section of the 1st level and 1st axis the index: df.xs('six',level=1,axis=0) -`Slicing a multi-index with xs, method #2 -`__ +`Slicing a MultiIndex with xs, method #2 +`__ .. ipython:: python @@ -386,14 +386,14 @@ To take the cross section of the 1st level and 1st axis the index: df.loc[(All,'Math'),('Exams')] df.loc[(All,'Math'),(All,'II')] -`Setting portions of a multi-index with xs -`__ +`Setting portions of a MultiIndex with xs +`__ Sorting ******* -`Sort by specific column or an ordered list of columns, with a multi-index -`__ +`Sort by specific column or an ordered list of columns, with a MultiIndex +`__ .. ipython:: python @@ -505,13 +505,11 @@ Unlike agg, apply's callable is passed a sub-DataFrame which gives you access to .. ipython:: python df = pd.DataFrame({'A' : [1, 1, 2, 2], 'B' : [1, -1, 1, 2]}) - gb = df.groupby('A') def replace(g): - mask = g < 0 - g.loc[mask] = g[~mask].mean() - return g + mask = g < 0 + return g.where(mask, g[~mask].mean()) gb.transform(replace) @@ -664,7 +662,7 @@ The :ref:`Pivot ` docs. `Plot pandas DataFrame with year over year data `__ -To create year and month crosstabulation: +To create year and month cross tabulation: .. ipython:: python @@ -677,7 +675,7 @@ To create year and month crosstabulation: Apply ***** -`Rolling Apply to Organize - Turning embedded lists into a multi-index frame +`Rolling Apply to Organize - Turning embedded lists into a MultiIndex frame `__ .. ipython:: python @@ -1029,8 +1027,8 @@ Skip row between header and data 01.01.1990 05:00;21;11;12;13 """ -Option 1: pass rows explicitly to skiprows -"""""""""""""""""""""""""""""""""""""""""" +Option 1: pass rows explicitly to skip rows +""""""""""""""""""""""""""""""""""""""""""" .. ipython:: python @@ -1225,6 +1223,57 @@ Computation `Numerical integration (sample-based) of a time series `__ +Correlation +*********** + +Often it's useful to obtain the lower (or upper) triangular form of a correlation matrix calculated from :func:`DataFrame.corr`. This can be achieved by passing a boolean mask to ``where`` as follows: + +.. ipython:: python + + df = pd.DataFrame(np.random.random(size=(100, 5))) + + corr_mat = df.corr() + mask = np.tril(np.ones_like(corr_mat, dtype=np.bool), k=-1) + + corr_mat.where(mask) + +The `method` argument within `DataFrame.corr` can accept a callable in addition to the named correlation types. Here we compute the `distance correlation `__ matrix for a `DataFrame` object. + +.. code-block:: python + + >>> def distcorr(x, y): + ... n = len(x) + ... a = np.zeros(shape=(n, n)) + ... b = np.zeros(shape=(n, n)) + ... + ... for i in range(n): + ... for j in range(i + 1, n): + ... a[i, j] = abs(x[i] - x[j]) + ... b[i, j] = abs(y[i] - y[j]) + ... + ... a += a.T + ... b += b.T + ... + ... a_bar = np.vstack([np.nanmean(a, axis=0)] * n) + ... b_bar = np.vstack([np.nanmean(b, axis=0)] * n) + ... + ... A = a - a_bar - a_bar.T + np.full(shape=(n, n), fill_value=a_bar.mean()) + ... B = b - b_bar - b_bar.T + np.full(shape=(n, n), fill_value=b_bar.mean()) + ... + ... cov_ab = np.sqrt(np.nansum(A * B)) / n + ... std_a = np.sqrt(np.sqrt(np.nansum(A**2)) / n) + ... std_b = np.sqrt(np.sqrt(np.nansum(B**2)) / n) + ... + ... return cov_ab / std_a / std_b + ... + >>> df = pd.DataFrame(np.random.normal(size=(100, 3))) + ... + >>> df.corr(method=distcorr) + 0 1 2 + 0 1.000000 0.171368 0.145302 + 1 0.171368 1.000000 0.189919 + 2 0.145302 0.189919 1.000000 + Timedeltas ---------- diff --git a/doc/source/developer.rst b/doc/source/developer.rst index b8bb2b2fcbe2f..f76af394abc48 100644 --- a/doc/source/developer.rst +++ b/doc/source/developer.rst @@ -81,20 +81,20 @@ The ``metadata`` field is ``None`` except for: omitted it is assumed to be nanoseconds. * ``categorical``: ``{'num_categories': K, 'ordered': is_ordered, 'type': $TYPE}`` - * Here ``'type'`` is optional, and can be a nested pandas type specification - here (but not categorical) + * Here ``'type'`` is optional, and can be a nested pandas type specification + here (but not categorical) * ``unicode``: ``{'encoding': encoding}`` - * The encoding is optional, and if not present is UTF-8 + * The encoding is optional, and if not present is UTF-8 * ``object``: ``{'encoding': encoding}``. Objects can be serialized and stored in ``BYTE_ARRAY`` Parquet columns. The encoding can be one of: - * ``'pickle'`` - * ``'msgpack'`` - * ``'bson'`` - * ``'json'`` + * ``'pickle'`` + * ``'msgpack'`` + * ``'bson'`` + * ``'json'`` * ``timedelta``: ``{'unit': 'ns'}``. The ``'unit'`` is optional, and if omitted it is assumed to be nanoseconds. This metadata is optional altogether diff --git a/doc/source/dsintro.rst b/doc/source/dsintro.rst index ca6cefac9e842..d02912294060c 100644 --- a/doc/source/dsintro.rst +++ b/doc/source/dsintro.rst @@ -51,9 +51,9 @@ labels are collectively referred to as the **index**. The basic method to create Here, ``data`` can be many different things: - - a Python dict - - an ndarray - - a scalar value (like 5) +* a Python dict +* an ndarray +* a scalar value (like 5) The passed **index** is a list of axis labels. Thus, this separates into a few cases depending on what **data is**: @@ -246,12 +246,12 @@ potentially different types. You can think of it like a spreadsheet or SQL table, or a dict of Series objects. It is generally the most commonly used pandas object. Like Series, DataFrame accepts many different kinds of input: - - Dict of 1D ndarrays, lists, dicts, or Series - - 2-D numpy.ndarray - - `Structured or record - `__ ndarray - - A ``Series`` - - Another ``DataFrame`` +* Dict of 1D ndarrays, lists, dicts, or Series +* 2-D numpy.ndarray +* `Structured or record + `__ ndarray +* A ``Series`` +* Another ``DataFrame`` Along with the data, you can optionally pass **index** (row labels) and **columns** (column labels) arguments. If you pass an index and / or columns, @@ -353,7 +353,7 @@ From a list of dicts From a dict of tuples ~~~~~~~~~~~~~~~~~~~~~ -You can automatically create a multi-indexed frame by passing a tuples +You can automatically create a MultiIndexed frame by passing a tuples dictionary. .. ipython:: python @@ -476,7 +476,7 @@ Assigning New Columns in Method Chains ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Inspired by `dplyr's -`__ +`__ ``mutate`` verb, DataFrame has an :meth:`~pandas.DataFrame.assign` method that allows you to easily create new columns that are potentially derived from existing columns. @@ -815,7 +815,7 @@ accessed like an attribute: df df.foo1 -The columns are also connected to the `IPython `__ +The columns are also connected to the `IPython `__ completion mechanism so they can be tab-completed: .. code-block:: ipython @@ -834,7 +834,7 @@ Panel a future version. See the section :ref:`Deprecate Panel `. Panel is a somewhat less-used, but still important container for 3-dimensional -data. The term `panel data `__ is +data. The term `panel data `__ is derived from econometrics and is partially responsible for the name pandas: pan(el)-da(ta)-s. The names for the 3 axes are intended to give some semantic meaning to describing operations involving panel data and, in particular, @@ -842,10 +842,10 @@ econometric analysis of panel data. However, for the strict purposes of slicing and dicing a collection of DataFrame objects, you may find the axis names slightly arbitrary: - - **items**: axis 0, each item corresponds to a DataFrame contained inside - - **major_axis**: axis 1, it is the **index** (rows) of each of the - DataFrames - - **minor_axis**: axis 2, it is the **columns** of each of the DataFrames +* **items**: axis 0, each item corresponds to a DataFrame contained inside +* **major_axis**: axis 1, it is the **index** (rows) of each of the + DataFrames +* **minor_axis**: axis 2, it is the **columns** of each of the DataFrames Construction of Panels works about like you would expect: @@ -1014,7 +1014,7 @@ Deprecate Panel Over the last few years, pandas has increased in both breadth and depth, with new features, datatype support, and manipulation routines. As a result, supporting efficient indexing and functional routines for ``Series``, ``DataFrame`` and ``Panel`` has contributed to an increasingly fragmented and -difficult-to-understand codebase. +difficult-to-understand code base. The 3-D structure of a ``Panel`` is much less common for many types of data analysis, than the 1-D of the ``Series`` or the 2-D of the ``DataFrame``. Going forward it makes sense for @@ -1023,8 +1023,8 @@ pandas to focus on these areas exclusively. Oftentimes, one can simply use a MultiIndex ``DataFrame`` for easily working with higher dimensional data. In addition, the ``xarray`` package was built from the ground up, specifically in order to -support the multi-dimensional analysis that is one of ``Panel`` s main usecases. -`Here is a link to the xarray panel-transition documentation `__. +support the multi-dimensional analysis that is one of ``Panel`` s main use cases. +`Here is a link to the xarray panel-transition documentation `__. .. ipython:: python :okwarning: @@ -1046,4 +1046,4 @@ Alternatively, one can convert to an xarray ``DataArray``. p.to_xarray() -You can see the full-documentation for the `xarray package `__. +You can see the full-documentation for the `xarray package `__. diff --git a/doc/source/ecosystem.rst b/doc/source/ecosystem.rst index 30cdb06b28487..edbd6629a617d 100644 --- a/doc/source/ecosystem.rst +++ b/doc/source/ecosystem.rst @@ -12,10 +12,13 @@ build powerful and more focused data tools. The creation of libraries that complement pandas' functionality also allows pandas development to remain focused around it's original requirements. -This is an in-exhaustive list of projects that build on pandas in order to provide -tools in the PyData space. +This is an inexhaustive list of projects that build on pandas in order to provide +tools in the PyData space. For a list of projects that depend on pandas, +see the +`libraries.io usage page for pandas `_ +or `search pypi for pandas `_. -We'd like to make it easier for users to find these project, if you know of other +We'd like to make it easier for users to find these projects, if you know of other substantial projects that you feel should be on this list, please let us know. @@ -24,8 +27,8 @@ substantial projects that you feel should be on this list, please let us know. Statistics and Machine Learning ------------------------------- -`Statsmodels `__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`Statsmodels `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Statsmodels is the prominent Python "statistics and econometrics library" and it has a long-standing special relationship with pandas. Statsmodels provides powerful statistics, @@ -35,18 +38,32 @@ Statsmodels leverages pandas objects as the underlying data container for comput `sklearn-pandas `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Use pandas DataFrames in your `scikit-learn `__ +Use pandas DataFrames in your `scikit-learn `__ ML pipeline. +`Featuretools `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Featuretools is a Python library for automated feature engineering built on top of pandas. It excels at transforming temporal and relational datasets into feature matrices for machine learning using reusable feature engineering "primitives". Users can contribute their own primitives in Python and share them with the rest of the community. .. _ecosystem.visualization: Visualization ------------- -`Bokeh `__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`Altair `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Altair is a declarative statistical visualization library for Python. +With Altair, you can spend more time understanding your data and its +meaning. Altair's API is simple, friendly and consistent and built on +top of the powerful Vega-Lite JSON specification. This elegant +simplicity produces beautiful and effective visualizations with a +minimal amount of code. Altair works with Pandas DataFrames. + + +`Bokeh `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Bokeh is a Python interactive visualization library for large datasets that natively uses the latest web technologies. Its goal is to provide elegant, concise construction of novel @@ -56,8 +73,8 @@ large data to thin clients. `seaborn `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Seaborn is a Python visualization library based on `matplotlib -`__. It provides a high-level, dataset-oriented +Seaborn is a Python visualization library based on +`matplotlib `__. It provides a high-level, dataset-oriented interface for creating attractive statistical graphics. The plotting functions in seaborn understand pandas objects and leverage pandas grouping operations internally to support concise specification of complex visualizations. Seaborn @@ -65,36 +82,27 @@ also goes beyond matplotlib and pandas with the option to perform statistical estimation while plotting, aggregating across observations and visualizing the fit of statistical models to emphasize patterns in a dataset. -`yhat/ggplot `__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`yhat/ggpy `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Hadley Wickham's `ggplot2 `__ is a foundational exploratory visualization package for the R language. -Based on `"The Grammar of Graphics" `__ it +Hadley Wickham's `ggplot2 `__ is a foundational exploratory visualization package for the R language. +Based on `"The Grammar of Graphics" `__ it provides a powerful, declarative and extremely general way to generate bespoke plots of any kind of data. It's really quite incredible. Various implementations to other languages are available, but a faithful implementation for Python users has long been missing. Although still young -(as of Jan-2014), the `yhat/ggplot `__ project has been +(as of Jan-2014), the `yhat/ggpy `__ project has been progressing quickly in that direction. -`Vincent `__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The `Vincent `__ project leverages `Vega `__ -(that in turn, leverages `d3 `__) to create -plots. Although functional, as of Summer 2016 the Vincent project has not been updated -in over two years and is `unlikely to receive further updates `__. - `IPython Vega `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Like Vincent, the `IPython Vega `__ project leverages `Vega -`__ to create plots, but primarily -targets the IPython Notebook environment. +`IPython Vega `__ leverages `Vega +`__ to create plots within Jupyter Notebook. `Plotly `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -`Plotly’s `__ `Python API `__ enables interactive figures and web shareability. Maps, 2D, 3D, and live-streaming graphs are rendered with WebGL and `D3.js `__. The library supports plotting directly from a pandas DataFrame and cloud-based collaboration. Users of `matplotlib, ggplot for Python, and Seaborn `__ can convert figures into interactive web-based plots. Plots can be drawn in `IPython Notebooks `__ , edited with R or MATLAB, modified in a GUI, or embedded in apps and dashboards. Plotly is free for unlimited sharing, and has `cloud `__, `offline `__, or `on-premise `__ accounts for private use. +`Plotly’s `__ `Python API `__ enables interactive figures and web shareability. Maps, 2D, 3D, and live-streaming graphs are rendered with WebGL and `D3.js `__. The library supports plotting directly from a pandas DataFrame and cloud-based collaboration. Users of `matplotlib, ggplot for Python, and Seaborn `__ can convert figures into interactive web-based plots. Plots can be drawn in `IPython Notebooks `__ , edited with R or MATLAB, modified in a GUI, or embedded in apps and dashboards. Plotly is free for unlimited sharing, and has `cloud `__, `offline `__, or `on-premise `__ accounts for private use. `QtPandas `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -108,24 +116,32 @@ library enables DataFrame visualization and manipulation in PyQt4 and PySide app IDE ------ -`IPython `__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`IPython `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IPython is an interactive command shell and distributed computing -environment. -IPython Notebook is a web application for creating IPython notebooks. -An IPython notebook is a JSON document containing an ordered list +environment. IPython tab completion works with Pandas methods and also +attributes like DataFrame columns. + +`Jupyter Notebook / Jupyter Lab `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Jupyter Notebook is a web application for creating Jupyter notebooks. +A Jupyter notebook is a JSON document containing an ordered list of input/output cells which can contain code, text, mathematics, plots and rich media. -IPython notebooks can be converted to a number of open standard output formats +Jupyter notebooks can be converted to a number of open standard output formats (HTML, HTML presentation slides, LaTeX, PDF, ReStructuredText, Markdown, -Python) through 'Download As' in the web interface and ``ipython nbconvert`` +Python) through 'Download As' in the web interface and ``jupyter convert`` in a shell. -Pandas DataFrames implement ``_repr_html_`` methods -which are utilized by IPython Notebook for displaying -(abbreviated) HTML tables. (Note: HTML tables may or may not be -compatible with non-HTML IPython output formats.) +Pandas DataFrames implement ``_repr_html_``and ``_repr_latex`` methods +which are utilized by Jupyter Notebook for displaying +(abbreviated) HTML or LaTeX tables. LaTeX output is properly escaped. +(Note: HTML tables may or may not be +compatible with non-HTML Jupyter output formats.) + +See :ref:`Options and Settings ` and :ref:`options.available ` +for pandas ``display.`` settings. `quantopian/qgrid `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -133,19 +149,35 @@ compatible with non-HTML IPython output formats.) qgrid is "an interactive grid for sorting and filtering DataFrames in IPython Notebook" built with SlickGrid. -`Spyder `__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`Spyder `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Spyder is a cross-platform PyQt-based IDE combining the editing, analysis, +debugging and profiling functionality of a software development tool with the +data exploration, interactive execution, deep inspection and rich visualization +capabilities of a scientific environment like MATLAB or Rstudio. + +Its `Variable Explorer `__ +allows users to view, manipulate and edit pandas ``Index``, ``Series``, +and ``DataFrame`` objects like a "spreadsheet", including copying and modifying +values, sorting, displaying a "heatmap", converting data types and more. +Pandas objects can also be renamed, duplicated, new columns added, +copyed/pasted to/from the clipboard (as TSV), and saved/loaded to/from a file. +Spyder can also import data from a variety of plain text and binary files +or the clipboard into a new pandas DataFrame via a sophisticated import wizard. -Spyder is a cross-platform Qt-based open-source Python IDE with -editing, testing, debugging, and introspection features. -Spyder can now introspect and display Pandas DataFrames and show -both "column wise min/max and global min/max coloring." +Most pandas classes, methods and data attributes can be autocompleted in +Spyder's `Editor `__ and +`IPython Console `__, +and Spyder's `Help pane `__ can retrieve +and render Numpydoc documentation on pandas objects in rich text with Sphinx +both automatically and on-demand. .. _ecosystem.api: API ------ +--- `pandas-datareader `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -156,14 +188,22 @@ See more in the `pandas-datareader docs `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -181,16 +221,16 @@ This package requires valid credentials for this API (non free). ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ pandaSDMX is a library to retrieve and acquire statistical data and metadata disseminated in -`SDMX `_ 2.1, an ISO-standard -widely used by institutions such as statistics offices, central banks, -and international organisations. pandaSDMX can expose datasets and related -structural metadata including dataflows, code-lists, -and datastructure definitions as pandas Series -or multi-indexed DataFrames. - +`SDMX `_ 2.1, an ISO-standard +widely used by institutions such as statistics offices, central banks, +and international organisations. pandaSDMX can expose datasets and related +structural metadata including data flows, code-lists, +and data structure definitions as pandas Series +or MultiIndexed DataFrames. + `fredapi `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -fredapi is a Python interface to the `Federal Reserve Economic Data (FRED) `__ +fredapi is a Python interface to the `Federal Reserve Economic Data (FRED) `__ provided by the Federal Reserve Bank of St. Louis. It works with both the FRED database and ALFRED database that contains point-in-time data (i.e. historic data revisions). fredapi provides a wrapper in Python to the FRED HTTP API, and also provides several convenient methods for parsing and analyzing point-in-time data from ALFRED. @@ -224,25 +264,24 @@ dimensional arrays, rather than the tabular data for which pandas excels. Out-of-core ------------- +`Blaze `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Blaze provides a standard API for doing computations with various +in-memory and on-disk backends: NumPy, Pandas, SQLAlchemy, MongoDB, PyTables, +PySpark. + `Dask `__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Dask is a flexible parallel computing library for analytics. Dask provides a familiar ``DataFrame`` interface for out-of-core, parallel and distributed computing. `Dask-ML `__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Dask-ML enables parallel and distributed machine learning using Dask alongside existing machine learning libraries like Scikit-Learn, XGBoost, and TensorFlow. - -`Blaze `__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Blaze provides a standard API for doing computations with various -in-memory and on-disk backends: NumPy, Pandas, SQLAlchemy, MongoDB, PyTables, -PySpark. - `Odo `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -252,15 +291,35 @@ PyTables, h5py, and pymongo to move data between non pandas formats. Its graph based approach is also extensible by end users for custom formats that may be too specific for the core of odo. +`Ray `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pandas on Ray is an early stage DataFrame library that wraps Pandas and transparently distributes the data and computation. The user does not need to know how many cores their system has, nor do they need to specify how to distribute the data. In fact, users can continue using their previous Pandas notebooks while experiencing a considerable speedup from Pandas on Ray, even on a single machine. Only a modification of the import statement is needed, as we demonstrate below. Once you’ve changed your import statement, you’re ready to use Pandas on Ray just like you would Pandas. + +.. code:: python + + # import pandas as pd + import ray.dataframe as pd + + +`Vaex `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Increasingly, packages are being built on top of pandas to address specific needs in data preparation, analysis and visualization. Vaex is a python library for Out-of-Core DataFrames (similar to Pandas), to visualize and explore big tabular datasets. It can calculate statistics such as mean, sum, count, standard deviation etc, on an N-dimensional grid up to a billion (10\ :sup:`9`) objects/rows per second. Visualization is done using histograms, density plots and 3d volume rendering, allowing interactive exploration of big data. Vaex uses memory mapping, zero memory copy policy and lazy computations for best performance (no memory wasted). + + * vaex.from_pandas + * vaex.to_pandas_df + + .. _ecosystem.data_validation: Data validation --------------- -`Engarde `__ +`Engarde `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Engarde is a lightweight library used to explicitly state your assumptions abour your datasets +Engarde is a lightweight library used to explicitly state your assumptions about your datasets and check that they're *actually* true. .. _ecosystem.extensions: diff --git a/doc/source/enhancingperf.rst b/doc/source/enhancingperf.rst index b786b1d0c134a..8f8a9fe3e50e0 100644 --- a/doc/source/enhancingperf.rst +++ b/doc/source/enhancingperf.rst @@ -32,7 +32,7 @@ Cython (Writing C extensions for pandas) ---------------------------------------- For many use cases writing pandas in pure Python and NumPy is sufficient. In some -computationally heavy applications however, it can be possible to achieve sizeable +computationally heavy applications however, it can be possible to achieve sizable speed-ups by offloading work to `cython `__. This tutorial assumes you have refactored as much as possible in Python, for example @@ -461,15 +461,15 @@ Supported Syntax These operations are supported by :func:`pandas.eval`: -- Arithmetic operations except for the left shift (``<<``) and right shift +* Arithmetic operations except for the left shift (``<<``) and right shift (``>>``) operators, e.g., ``df + 2 * pi / s ** 4 % 42 - the_golden_ratio`` -- Comparison operations, including chained comparisons, e.g., ``2 < df < df2`` -- Boolean operations, e.g., ``df < df2 and df3 < df4 or not df_bool`` -- ``list`` and ``tuple`` literals, e.g., ``[1, 2]`` or ``(1, 2)`` -- Attribute access, e.g., ``df.a`` -- Subscript expressions, e.g., ``df[0]`` -- Simple variable evaluation, e.g., ``pd.eval('df')`` (this is not very useful) -- Math functions: `sin`, `cos`, `exp`, `log`, `expm1`, `log1p`, +* Comparison operations, including chained comparisons, e.g., ``2 < df < df2`` +* Boolean operations, e.g., ``df < df2 and df3 < df4 or not df_bool`` +* ``list`` and ``tuple`` literals, e.g., ``[1, 2]`` or ``(1, 2)`` +* Attribute access, e.g., ``df.a`` +* Subscript expressions, e.g., ``df[0]`` +* Simple variable evaluation, e.g., ``pd.eval('df')`` (this is not very useful) +* Math functions: `sin`, `cos`, `exp`, `log`, `expm1`, `log1p`, `sqrt`, `sinh`, `cosh`, `tanh`, `arcsin`, `arccos`, `arctan`, `arccosh`, `arcsinh`, `arctanh`, `abs` and `arctan2`. @@ -477,22 +477,22 @@ This Python syntax is **not** allowed: * Expressions - - Function calls other than math functions. - - ``is``/``is not`` operations - - ``if`` expressions - - ``lambda`` expressions - - ``list``/``set``/``dict`` comprehensions - - Literal ``dict`` and ``set`` expressions - - ``yield`` expressions - - Generator expressions - - Boolean expressions consisting of only scalar values + * Function calls other than math functions. + * ``is``/``is not`` operations + * ``if`` expressions + * ``lambda`` expressions + * ``list``/``set``/``dict`` comprehensions + * Literal ``dict`` and ``set`` expressions + * ``yield`` expressions + * Generator expressions + * Boolean expressions consisting of only scalar values * Statements - - Neither `simple `__ - nor `compound `__ - statements are allowed. This includes things like ``for``, ``while``, and - ``if``. + * Neither `simple `__ + nor `compound `__ + statements are allowed. This includes things like ``for``, ``while``, and + ``if``. @@ -806,7 +806,7 @@ truncate any strings that are more than 60 characters in length. Second, we can't pass ``object`` arrays to ``numexpr`` thus string comparisons must be evaluated in Python space. -The upshot is that this *only* applies to object-dtype'd expressions. So, if +The upshot is that this *only* applies to object-dtype expressions. So, if you have an expression--for example .. ipython:: python diff --git a/doc/source/extending.rst b/doc/source/extending.rst index f665b219a7bd1..1e8a8e50dd9e3 100644 --- a/doc/source/extending.rst +++ b/doc/source/extending.rst @@ -61,7 +61,7 @@ Extension Types .. warning:: - The :class:`pandas.api.extension.ExtensionDtype` and :class:`pandas.api.extension.ExtensionArray` APIs are new and + The :class:`pandas.api.extensions.ExtensionDtype` and :class:`pandas.api.extensions.ExtensionArray` APIs are new and experimental. They may change between versions without warning. Pandas defines an interface for implementing data types and arrays that *extend* @@ -79,10 +79,10 @@ on :ref:`ecosystem.extensions`. The interface consists of two classes. -:class:`~pandas.api.extension.ExtensionDtype` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:class:`~pandas.api.extensions.ExtensionDtype` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -A :class:`pandas.api.extension.ExtensionDtype` is similar to a ``numpy.dtype`` object. It describes the +A :class:`pandas.api.extensions.ExtensionDtype` is similar to a ``numpy.dtype`` object. It describes the data type. Implementors are responsible for a few unique items like the name. One particularly important item is the ``type`` property. This should be the @@ -91,8 +91,16 @@ extension array for IP Address data, this might be ``ipaddress.IPv4Address``. See the `extension dtype source`_ for interface definition. -:class:`~pandas.api.extension.ExtensionArray` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. versionadded:: 0.24.0 + +:class:`pandas.api.extension.ExtensionDtype` can be registered to pandas to allow creation via a string dtype name. +This allows one to instantiate ``Series`` and ``.astype()`` with a registered string name, for +example ``'category'`` is a registered string accessor for the ``CategoricalDtype``. + +See the `extension dtype dtypes`_ for more on how to register dtypes. + +:class:`~pandas.api.extensions.ExtensionArray` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This class provides all the array-like functionality. ExtensionArrays are limited to 1 dimension. An ExtensionArray is linked to an ExtensionDtype via the @@ -113,6 +121,79 @@ by some other storage type, like Python lists. See the `extension array source`_ for the interface definition. The docstrings and comments contain guidance for properly implementing the interface. +.. _extending.extension.operator: + +:class:`~pandas.api.extensions.ExtensionArray` Operator Support +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 0.24.0 + +By default, there are no operators defined for the class :class:`~pandas.api.extensions.ExtensionArray`. +There are two approaches for providing operator support for your ExtensionArray: + +1. Define each of the operators on your ``ExtensionArray`` subclass. +2. Use an operator implementation from pandas that depends on operators that are already defined + on the underlying elements (scalars) of the ExtensionArray. + +.. note:: + + Regardless of the approach, you may want to set ``__array_priority__`` + if you want your implementation to be called when involved in binary operations + with NumPy arrays. + +For the first approach, you define selected operators, e.g., ``__add__``, ``__le__``, etc. that +you want your ``ExtensionArray`` subclass to support. + +The second approach assumes that the underlying elements (i.e., scalar type) of the ``ExtensionArray`` +have the individual operators already defined. In other words, if your ``ExtensionArray`` +named ``MyExtensionArray`` is implemented so that each element is an instance +of the class ``MyExtensionElement``, then if the operators are defined +for ``MyExtensionElement``, the second approach will automatically +define the operators for ``MyExtensionArray``. + +A mixin class, :class:`~pandas.api.extensions.ExtensionScalarOpsMixin` supports this second +approach. If developing an ``ExtensionArray`` subclass, for example ``MyExtensionArray``, +can simply include ``ExtensionScalarOpsMixin`` as a parent class of ``MyExtensionArray``, +and then call the methods :meth:`~MyExtensionArray._add_arithmetic_ops` and/or +:meth:`~MyExtensionArray._add_comparison_ops` to hook the operators into +your ``MyExtensionArray`` class, as follows: + +.. code-block:: python + + class MyExtensionArray(ExtensionArray, ExtensionScalarOpsMixin): + pass + + MyExtensionArray._add_arithmetic_ops() + MyExtensionArray._add_comparison_ops() + + +.. note:: + + Since ``pandas`` automatically calls the underlying operator on each + element one-by-one, this might not be as performant as implementing your own + version of the associated operators directly on the ``ExtensionArray``. + +For arithmetic operations, this implementation will try to reconstruct a new +``ExtensionArray`` with the result of the element-wise operation. Whether +or not that succeeds depends on whether the operation returns a result +that's valid for the ``ExtensionArray``. If an ``ExtensionArray`` cannot +be reconstructed, an ndarray containing the scalars returned instead. + +For ease of implementation and consistency with operations between pandas +and NumPy ndarrays, we recommend *not* handling Series and Indexes in your binary ops. +Instead, you should detect these cases and return ``NotImplemented``. +When pandas encounters an operation like ``op(Series, ExtensionArray)``, pandas +will + +1. unbox the array from the ``Series`` (roughly ``Series.values``) +2. call ``result = op(values, ExtensionArray)`` +3. re-box the result in a ``Series`` + +.. _extending.extension.testing: + +Testing Extension Arrays +^^^^^^^^^^^^^^^^^^^^^^^^ + We provide a test suite for ensuring that your extension arrays satisfy the expected behavior. To use the test suite, you must provide several pytest fixtures and inherit from the base test class. The required fixtures are found in @@ -131,6 +212,7 @@ To use a test, subclass it: See https://github.com/pandas-dev/pandas/blob/master/pandas/tests/extension/base/__init__.py for a list of all the tests available. +.. _extension dtype dtypes: https://github.com/pandas-dev/pandas/blob/master/pandas/core/dtypes/dtypes.py .. _extension dtype source: https://github.com/pandas-dev/pandas/blob/master/pandas/core/dtypes/base.py .. _extension array source: https://github.com/pandas-dev/pandas/blob/master/pandas/core/arrays/base.py @@ -167,18 +249,18 @@ you can retain subclasses through ``pandas`` data manipulations. There are 3 constructor properties to be defined: -- ``_constructor``: Used when a manipulation result has the same dimesions as the original. -- ``_constructor_sliced``: Used when a manipulation result has one lower dimension(s) as the original, such as ``DataFrame`` single columns slicing. -- ``_constructor_expanddim``: Used when a manipulation result has one higher dimension as the original, such as ``Series.to_frame()`` and ``DataFrame.to_panel()``. +* ``_constructor``: Used when a manipulation result has the same dimensions as the original. +* ``_constructor_sliced``: Used when a manipulation result has one lower dimension(s) as the original, such as ``DataFrame`` single columns slicing. +* ``_constructor_expanddim``: Used when a manipulation result has one higher dimension as the original, such as ``Series.to_frame()`` and ``DataFrame.to_panel()``. Following table shows how ``pandas`` data structures define constructor properties by default. =========================== ======================= ============= -Property Attributes ``Series`` ``DataFrame`` +Property Attributes ``Series`` ``DataFrame`` =========================== ======================= ============= -``_constructor`` ``Series`` ``DataFrame`` -``_constructor_sliced`` ``NotImplementedError`` ``Series`` -``_constructor_expanddim`` ``DataFrame`` ``Panel`` +``_constructor`` ``Series`` ``DataFrame`` +``_constructor_sliced`` ``NotImplementedError`` ``Series`` +``_constructor_expanddim`` ``DataFrame`` ``Panel`` =========================== ======================= ============= Below example shows how to define ``SubclassedSeries`` and ``SubclassedDataFrame`` overriding constructor properties. diff --git a/doc/source/gotchas.rst b/doc/source/gotchas.rst index b7042ef390018..79e312ca12833 100644 --- a/doc/source/gotchas.rst +++ b/doc/source/gotchas.rst @@ -193,9 +193,9 @@ Choice of ``NA`` representation For lack of ``NA`` (missing) support from the ground up in NumPy and Python in general, we were given the difficult choice between either: -- A *masked array* solution: an array of data and an array of boolean values +* A *masked array* solution: an array of data and an array of boolean values indicating whether a value is there or is missing. -- Using a special sentinel value, bit pattern, or set of sentinel values to +* Using a special sentinel value, bit pattern, or set of sentinel values to denote ``NA`` across the dtypes. For many reasons we chose the latter. After years of production use it has diff --git a/doc/source/groupby.rst b/doc/source/groupby.rst index da13a34cccfea..17a723e2a2f42 100644 --- a/doc/source/groupby.rst +++ b/doc/source/groupby.rst @@ -22,36 +22,36 @@ Group By: split-apply-combine By "group by" we are referring to a process involving one or more of the following steps: - - **Splitting** the data into groups based on some criteria. - - **Applying** a function to each group independently. - - **Combining** the results into a data structure. +* **Splitting** the data into groups based on some criteria. +* **Applying** a function to each group independently. +* **Combining** the results into a data structure. Out of these, the split step is the most straightforward. In fact, in many situations we may wish to split the data set into groups and do something with those groups. In the apply step, we might wish to one of the following: - - **Aggregation**: compute a summary statistic (or statistics) for each - group. Some examples: +* **Aggregation**: compute a summary statistic (or statistics) for each + group. Some examples: - - Compute group sums or means. - - Compute group sizes / counts. + * Compute group sums or means. + * Compute group sizes / counts. - - **Transformation**: perform some group-specific computations and return a - like-indexed object. Some examples: +* **Transformation**: perform some group-specific computations and return a + like-indexed object. Some examples: - - Standardize data (zscore) within a group. - - Filling NAs within groups with a value derived from each group. + * Standardize data (zscore) within a group. + * Filling NAs within groups with a value derived from each group. - - **Filtration**: discard some groups, according to a group-wise computation - that evaluates True or False. Some examples: +* **Filtration**: discard some groups, according to a group-wise computation + that evaluates True or False. Some examples: - - Discard data that belongs to groups with only a few members. - - Filter out data based on the group sum or mean. + * Discard data that belongs to groups with only a few members. + * Filter out data based on the group sum or mean. - - Some combination of the above: GroupBy will examine the results of the apply - step and try to return a sensibly combined result if it doesn't fit into - either of the above two categories. +* Some combination of the above: GroupBy will examine the results of the apply + step and try to return a sensibly combined result if it doesn't fit into + either of the above two categories. Since the set of object instance methods on pandas data structures are generally rich and expressive, we often simply want to invoke, say, a DataFrame function @@ -88,27 +88,24 @@ object (more on what the GroupBy object is later), you may do the following: The mapping can be specified many different ways: - - A Python function, to be called on each of the axis labels. - - A list or NumPy array of the same length as the selected axis. - - A dict or ``Series``, providing a ``label -> group name`` mapping. - - For ``DataFrame`` objects, a string indicating a column to be used to group. - Of course ``df.groupby('A')`` is just syntactic sugar for - ``df.groupby(df['A'])``, but it makes life simpler. - - For ``DataFrame`` objects, a string indicating an index level to be used to - group. - - A list of any of the above things. +* A Python function, to be called on each of the axis labels. +* A list or NumPy array of the same length as the selected axis. +* A dict or ``Series``, providing a ``label -> group name`` mapping. +* For ``DataFrame`` objects, a string indicating a column to be used to group. + Of course ``df.groupby('A')`` is just syntactic sugar for + ``df.groupby(df['A'])``, but it makes life simpler. +* For ``DataFrame`` objects, a string indicating an index level to be used to + group. +* A list of any of the above things. Collectively we refer to the grouping objects as the **keys**. For example, consider the following ``DataFrame``: .. note:: - .. versionadded:: 0.20 - A string passed to ``groupby`` may refer to either a column or an index level. - If a string matches both a column name and an index level name then a warning is - issued and the column takes precedence. This will result in an ambiguity error - in a future version. + If a string matches both a column name and an index level name, a + ``ValueError`` will be raised. .. ipython:: python @@ -128,6 +125,17 @@ We could naturally group by either the ``A`` or ``B`` columns, or both: grouped = df.groupby('A') grouped = df.groupby(['A', 'B']) +.. versionadded:: 0.24 + +If we also have a MultiIndex on columns ``A`` and ``B``, we can group by all +but the specified columns + +.. ipython:: python + + df2 = df.set_index(['A', 'B']) + grouped = df2.groupby(level=df2.index.names.difference(['B'])) + grouped.sum() + These will split the DataFrame on its index (rows). We could also split by the columns: @@ -389,7 +397,7 @@ This is mainly syntactic sugar for the alternative and much more verbose: Additionally this method avoids recomputing the internal grouping information derived from the passed key. -.. _groupby.iterating: +.. _groupby.iterating-label: Iterating through groups ------------------------ @@ -415,8 +423,7 @@ In the case of grouping by multiple keys, the group name will be a tuple: ...: print(group) ...: -It's standard Python-fu but remember you can unpack the tuple in the for loop -statement if you wish: ``for (k1, k2), group in grouped:``. +See :ref:`timeseries.iterating-label`. Selecting a group ----------------- @@ -680,8 +687,7 @@ match the shape of the input array. data_range = lambda x: x.max() - x.min() ts.groupby(key).transform(data_range) -Alternatively the built-in methods can be could be used to produce the same -outputs +Alternatively, the built-in methods could be used to produce the same outputs. .. ipython:: python @@ -994,7 +1000,7 @@ is only interesting over one column (here ``colname``), it may be filtered Handling of (un)observed Categorical values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When using a ``Categorical`` grouper (as a single grouper, or as part of multipler groupers), the ``observed`` keyword +When using a ``Categorical`` grouper (as a single grouper, or as part of multiple groupers), the ``observed`` keyword controls whether to return a cartesian product of all possible groupers values (``observed=False``) or only those that are observed groupers (``observed=True``). @@ -1010,7 +1016,7 @@ Show only the observed values: pd.Series([1, 1, 1]).groupby(pd.Categorical(['a', 'a', 'a'], categories=['a', 'b']), observed=True).count() -The returned dtype of the grouped will *always* include *all* of the catergories that were grouped. +The returned dtype of the grouped will *always* include *all* of the categories that were grouped. .. ipython:: python diff --git a/doc/source/index.rst.template b/doc/source/index.rst.template index f5ac7b77f4db1..d2b88e794e51e 100644 --- a/doc/source/index.rst.template +++ b/doc/source/index.rst.template @@ -14,15 +14,15 @@ pandas: powerful Python data analysis toolkit **Binary Installers:** https://pypi.org/project/pandas -**Source Repository:** http://github.com/pandas-dev/pandas +**Source Repository:** https://github.com/pandas-dev/pandas **Issues & Ideas:** https://github.com/pandas-dev/pandas/issues -**Q&A Support:** http://stackoverflow.com/questions/tagged/pandas +**Q&A Support:** https://stackoverflow.com/questions/tagged/pandas -**Developer Mailing List:** http://groups.google.com/group/pydata +**Developer Mailing List:** https://groups.google.com/forum/#!forum/pydata -**pandas** is a `Python `__ package providing fast, +**pandas** is a `Python `__ package providing fast, flexible, and expressive data structures designed to make working with "relational" or "labeled" data both easy and intuitive. It aims to be the fundamental high-level building block for doing practical, **real world** data @@ -45,7 +45,7 @@ and :class:`DataFrame` (2-dimensional), handle the vast majority of typical use cases in finance, statistics, social science, and many areas of engineering. For R users, :class:`DataFrame` provides everything that R's ``data.frame`` provides and much more. pandas is built on top of `NumPy -`__ and is intended to integrate well within a scientific +`__ and is intended to integrate well within a scientific computing environment with many other 3rd party libraries. Here are just a few of the things that pandas does well: @@ -86,13 +86,13 @@ is the ideal tool for all of these tasks. Some other notes - pandas is **fast**. Many of the low-level algorithmic bits have been - extensively tweaked in `Cython `__ code. However, as with + extensively tweaked in `Cython `__ code. However, as with anything else generalization usually sacrifices performance. So if you focus on one feature for your application you may be able to create a faster specialized tool. - pandas is a dependency of `statsmodels - `__, making it an important part of the + `__, making it an important part of the statistical computing ecosystem in Python. - pandas has been used extensively in production in financial applications. @@ -101,7 +101,7 @@ Some other notes This documentation assumes general familiarity with NumPy. If you haven't used NumPy much or at all, do invest some time in `learning about NumPy - `__ first. + `__ first. See the package overview for more detail about what's in the library. diff --git a/doc/source/indexing.rst b/doc/source/indexing.rst index e834efd1cb6d1..1c63acce6e3fa 100644 --- a/doc/source/indexing.rst +++ b/doc/source/indexing.rst @@ -17,10 +17,10 @@ Indexing and Selecting Data The axis labeling information in pandas objects serves many purposes: - - Identifies data (i.e. provides *metadata*) using known indicators, - important for analysis, visualization, and interactive console display. - - Enables automatic and explicit data alignment. - - Allows intuitive getting and setting of subsets of the data set. +* Identifies data (i.e. provides *metadata*) using known indicators, + important for analysis, visualization, and interactive console display. +* Enables automatic and explicit data alignment. +* Allows intuitive getting and setting of subsets of the data set. In this section, we will focus on the final point: namely, how to slice, dice, and generally get and set subsets of pandas objects. The primary focus will be @@ -62,37 +62,37 @@ Object selection has had a number of user-requested additions in order to support more explicit location based indexing. Pandas now supports three types of multi-axis indexing. -- ``.loc`` is primarily label based, but may also be used with a boolean array. ``.loc`` will raise ``KeyError`` when the items are not found. Allowed inputs are: +* ``.loc`` is primarily label based, but may also be used with a boolean array. ``.loc`` will raise ``KeyError`` when the items are not found. Allowed inputs are: - - A single label, e.g. ``5`` or ``'a'`` (Note that ``5`` is interpreted as a - *label* of the index. This use is **not** an integer position along the - index.). - - A list or array of labels ``['a', 'b', 'c']``. - - A slice object with labels ``'a':'f'`` (Note that contrary to usual python - slices, **both** the start and the stop are included, when present in the - index! See :ref:`Slicing with labels - `.). - - A boolean array - - A ``callable`` function with one argument (the calling Series, DataFrame or Panel) and - that returns valid output for indexing (one of the above). + * A single label, e.g. ``5`` or ``'a'`` (Note that ``5`` is interpreted as a + *label* of the index. This use is **not** an integer position along the + index.). + * A list or array of labels ``['a', 'b', 'c']``. + * A slice object with labels ``'a':'f'`` (Note that contrary to usual python + slices, **both** the start and the stop are included, when present in the + index! See :ref:`Slicing with labels + `.). + * A boolean array + * A ``callable`` function with one argument (the calling Series, DataFrame or Panel) and + that returns valid output for indexing (one of the above). .. versionadded:: 0.18.1 See more at :ref:`Selection by Label `. -- ``.iloc`` is primarily integer position based (from ``0`` to +* ``.iloc`` is primarily integer position based (from ``0`` to ``length-1`` of the axis), but may also be used with a boolean array. ``.iloc`` will raise ``IndexError`` if a requested indexer is out-of-bounds, except *slice* indexers which allow out-of-bounds indexing. (this conforms with Python/NumPy *slice* semantics). Allowed inputs are: - - An integer e.g. ``5``. - - A list or array of integers ``[4, 3, 0]``. - - A slice object with ints ``1:7``. - - A boolean array. - - A ``callable`` function with one argument (the calling Series, DataFrame or Panel) and - that returns valid output for indexing (one of the above). + * An integer e.g. ``5``. + * A list or array of integers ``[4, 3, 0]``. + * A slice object with ints ``1:7``. + * A boolean array. + * A ``callable`` function with one argument (the calling Series, DataFrame or Panel) and + that returns valid output for indexing (one of the above). .. versionadded:: 0.18.1 @@ -100,7 +100,7 @@ of multi-axis indexing. :ref:`Advanced Indexing ` and :ref:`Advanced Hierarchical `. -- ``.loc``, ``.iloc``, and also ``[]`` indexing can accept a ``callable`` as indexer. See more at :ref:`Selection By Callable `. +* ``.loc``, ``.iloc``, and also ``[]`` indexing can accept a ``callable`` as indexer. See more at :ref:`Selection By Callable `. Getting values from an object with multi-axes selection uses the following notation (using ``.loc`` as an example, but the following applies to ``.iloc`` as @@ -343,14 +343,14 @@ Integers are valid labels, but they refer to the label **and not the position**. The ``.loc`` attribute is the primary access method. The following are valid inputs: -- A single label, e.g. ``5`` or ``'a'`` (Note that ``5`` is interpreted as a *label* of the index. This use is **not** an integer position along the index.). -- A list or array of labels ``['a', 'b', 'c']``. -- A slice object with labels ``'a':'f'`` (Note that contrary to usual python +* A single label, e.g. ``5`` or ``'a'`` (Note that ``5`` is interpreted as a *label* of the index. This use is **not** an integer position along the index.). +* A list or array of labels ``['a', 'b', 'c']``. +* A slice object with labels ``'a':'f'`` (Note that contrary to usual python slices, **both** the start and the stop are included, when present in the index! See :ref:`Slicing with labels `.). -- A boolean array. -- A ``callable``, see :ref:`Selection By Callable `. +* A boolean array. +* A ``callable``, see :ref:`Selection By Callable `. .. ipython:: python @@ -445,11 +445,11 @@ Pandas provides a suite of methods in order to get **purely integer based indexi The ``.iloc`` attribute is the primary access method. The following are valid inputs: -- An integer e.g. ``5``. -- A list or array of integers ``[4, 3, 0]``. -- A slice object with ints ``1:7``. -- A boolean array. -- A ``callable``, see :ref:`Selection By Callable `. +* An integer e.g. ``5``. +* A list or array of integers ``[4, 3, 0]``. +* A slice object with ints ``1:7``. +* A boolean array. +* A ``callable``, see :ref:`Selection By Callable `. .. ipython:: python @@ -599,8 +599,8 @@ bit of user confusion over the years. The recommended methods of indexing are: -- ``.loc`` if you want to *label* index. -- ``.iloc`` if you want to *positionally* index. +* ``.loc`` if you want to *label* index. +* ``.iloc`` if you want to *positionally* index. .. ipython:: python @@ -700,7 +700,7 @@ Current Behavior Reindexing ~~~~~~~~~~ -The idiomatic way to achieve selecting potentially not-found elmenents is via ``.reindex()``. See also the section on :ref:`reindexing `. +The idiomatic way to achieve selecting potentially not-found elements is via ``.reindex()``. See also the section on :ref:`reindexing `. .. ipython:: python @@ -1455,15 +1455,15 @@ If you want to identify and remove duplicate rows in a DataFrame, there are two methods that will help: ``duplicated`` and ``drop_duplicates``. Each takes as an argument the columns to use to identify duplicated rows. -- ``duplicated`` returns a boolean vector whose length is the number of rows, and which indicates whether a row is duplicated. -- ``drop_duplicates`` removes duplicate rows. +* ``duplicated`` returns a boolean vector whose length is the number of rows, and which indicates whether a row is duplicated. +* ``drop_duplicates`` removes duplicate rows. By default, the first observed row of a duplicate set is considered unique, but each method has a ``keep`` parameter to specify targets to be kept. -- ``keep='first'`` (default): mark / drop duplicates except for the first occurrence. -- ``keep='last'``: mark / drop duplicates except for the last occurrence. -- ``keep=False``: mark / drop all duplicates. +* ``keep='first'`` (default): mark / drop duplicates except for the first occurrence. +* ``keep='last'``: mark / drop duplicates except for the last occurrence. +* ``keep=False``: mark / drop all duplicates. .. ipython:: python diff --git a/doc/source/install.rst b/doc/source/install.rst index 6054be112f52c..89f7b580303f5 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -31,7 +31,7 @@ PyPI and through conda. Starting **January 1, 2019**, all releases will be Python 3 only. If there are people interested in continued support for Python 2.7 past December -31, 2018 (either backporting bugfixes or funding) please reach out to the +31, 2018 (either backporting bug fixes or funding) please reach out to the maintainers on the issue tracker. For more information, see the `Python 3 statement`_ and the `Porting to Python 3 guide`_. @@ -43,7 +43,7 @@ For more information, see the `Python 3 statement`_ and the `Porting to Python 3 Python version support ---------------------- -Officially Python 2.7, 3.5, and 3.6. +Officially Python 2.7, 3.5, 3.6, and 3.7. Installing pandas ----------------- @@ -199,10 +199,11 @@ Running the test suite ---------------------- pandas is equipped with an exhaustive set of unit tests, covering about 97% of -the codebase as of this writing. To run it on your machine to verify that +the code base as of this writing. To run it on your machine to verify that everything is working (and that you have all of the dependencies, soft and hard, installed), make sure you have `pytest -`__ and run: +`__ >= 3.6 and `Hypothesis +`__ >= 3.58, then run: :: @@ -210,7 +211,7 @@ installed), make sure you have `pytest >>> pd.test() running: pytest --skip-slow --skip-network C:\Users\TP\Anaconda3\envs\py36\lib\site-packages\pandas ============================= test session starts ============================= - platform win32 -- Python 3.6.2, pytest-3.2.1, py-1.4.34, pluggy-0.4.0 + platform win32 -- Python 3.6.2, pytest-3.6.0, py-1.4.34, pluggy-0.4.0 rootdir: C:\Users\TP\Documents\Python\pandasdev\pandas, inifile: setup.cfg collected 12145 items / 3 skipped @@ -224,8 +225,8 @@ Dependencies ------------ * `setuptools `__: 24.2.0 or higher -* `NumPy `__: 1.9.0 or higher -* `python-dateutil `__: 2.5.0 or higher +* `NumPy `__: 1.12.0 or higher +* `python-dateutil `__: 2.5.0 or higher * `pytz `__ .. _install.recommended_dependencies: @@ -235,11 +236,11 @@ Recommended Dependencies * `numexpr `__: for accelerating certain numerical operations. ``numexpr`` uses multiple cores as well as smart chunking and caching to achieve large speedups. - If installed, must be Version 2.4.6 or higher. + If installed, must be Version 2.6.1 or higher. * `bottleneck `__: for accelerating certain types of ``nan`` evaluations. ``bottleneck`` uses specialized cython routines to achieve large speedups. If installed, - must be Version 1.0.0 or higher. + must be Version 1.2.0 or higher. .. note:: @@ -253,29 +254,30 @@ Optional Dependencies ~~~~~~~~~~~~~~~~~~~~~ * `Cython `__: Only necessary to build development - version. Version 0.24 or higher. -* `SciPy `__: miscellaneous statistical functions, Version 0.14.0 or higher + version. Version 0.28.2 or higher. +* `SciPy `__: miscellaneous statistical functions, Version 0.18.1 or higher * `xarray `__: pandas like handling for > 2 dims, needed for converting Panels to xarray objects. Version 0.7.0 or higher is recommended. -* `PyTables `__: necessary for HDF5-based storage. Version 3.0.0 or higher required, Version 3.2.1 or higher highly recommended. -* `Feather Format `__: necessary for feather-based storage, version 0.3.1 or higher. -* `Apache Parquet `__, either `pyarrow `__ (>= 0.4.1) or `fastparquet `__ (>= 0.0.6) for parquet-based storage. The `snappy `__ and `brotli `__ are available for compression support. +* `PyTables `__: necessary for HDF5-based storage, Version 3.4.2 or higher +* `pyarrow `__ (>= 0.7.0): necessary for feather-based storage. +* `Apache Parquet `__, either `pyarrow `__ (>= 0.7.0) or `fastparquet `__ (>= 0.1.2) for parquet-based storage. The `snappy `__ and `brotli `__ are available for compression support. * `SQLAlchemy `__: for SQL database support. Version 0.8.1 or higher recommended. Besides SQLAlchemy, you also need a database specific driver. You can find an overview of supported drivers for each SQL dialect in the `SQLAlchemy docs `__. Some common drivers are: - * `psycopg2 `__: for PostgreSQL - * `pymysql `__: for MySQL. - * `SQLite `__: for SQLite, this is included in Python's standard library by default. + * `psycopg2 `__: for PostgreSQL + * `pymysql `__: for MySQL. + * `SQLite `__: for SQLite, this is included in Python's standard library by default. -* `matplotlib `__: for plotting, Version 1.4.3 or higher. +* `matplotlib `__: for plotting, Version 2.0.0 or higher. * For Excel I/O: - * `xlrd/xlwt `__: Excel reading (xlrd) and writing (xlwt) - * `openpyxl `__: openpyxl version 2.4.0 - for writing .xlsx files (xlrd >= 0.9.0) - * `XlsxWriter `__: Alternative Excel writer + * `xlrd/xlwt `__: Excel reading (xlrd) and writing (xlwt) + * `openpyxl `__: openpyxl version 2.4.0 + for writing .xlsx files (xlrd >= 0.9.0) + * `XlsxWriter `__: Alternative Excel writer * `Jinja2 `__: Template engine for conditional HTML formatting. * `s3fs `__: necessary for Amazon S3 access (s3fs >= 0.0.7). * `blosc `__: for msgpack compression using ``blosc`` +* `gcsfs `__: necessary for Google Cloud Storage access (gcsfs >= 0.1.0). * One of `qtpy `__ (requires PyQt or PySide), `PyQt5 `__, diff --git a/doc/source/internals.rst b/doc/source/internals.rst index b120e3a98db7f..fce99fc633440 100644 --- a/doc/source/internals.rst +++ b/doc/source/internals.rst @@ -24,24 +24,24 @@ Indexing In pandas there are a few objects implemented which can serve as valid containers for the axis labels: -- ``Index``: the generic "ordered set" object, an ndarray of object dtype +* ``Index``: the generic "ordered set" object, an ndarray of object dtype assuming nothing about its contents. The labels must be hashable (and likely immutable) and unique. Populates a dict of label to location in Cython to do ``O(1)`` lookups. -- ``Int64Index``: a version of ``Index`` highly optimized for 64-bit integer +* ``Int64Index``: a version of ``Index`` highly optimized for 64-bit integer data, such as time stamps -- ``Float64Index``: a version of ``Index`` highly optimized for 64-bit float data -- ``MultiIndex``: the standard hierarchical index object -- ``DatetimeIndex``: An Index object with ``Timestamp`` boxed elements (impl are the int64 values) -- ``TimedeltaIndex``: An Index object with ``Timedelta`` boxed elements (impl are the in64 values) -- ``PeriodIndex``: An Index object with Period elements +* ``Float64Index``: a version of ``Index`` highly optimized for 64-bit float data +* ``MultiIndex``: the standard hierarchical index object +* ``DatetimeIndex``: An Index object with ``Timestamp`` boxed elements (impl are the int64 values) +* ``TimedeltaIndex``: An Index object with ``Timedelta`` boxed elements (impl are the in64 values) +* ``PeriodIndex``: An Index object with Period elements There are functions that make the creation of a regular index easy: -- ``date_range``: fixed frequency date range generated from a time rule or +* ``date_range``: fixed frequency date range generated from a time rule or DateOffset. An ndarray of Python datetime objects -- ``period_range``: fixed frequency date range generated from a time rule or - DateOffset. An ndarray of ``Period`` objects, representing Timespans +* ``period_range``: fixed frequency date range generated from a time rule or + DateOffset. An ndarray of ``Period`` objects, representing timespans The motivation for having an ``Index`` class in the first place was to enable different implementations of indexing. This means that it's possible for you, @@ -52,22 +52,22 @@ From an internal implementation point of view, the relevant methods that an ``Index`` must define are one or more of the following (depending on how incompatible the new object internals are with the ``Index`` functions): -- ``get_loc``: returns an "indexer" (an integer, or in some cases a +* ``get_loc``: returns an "indexer" (an integer, or in some cases a slice object) for a label -- ``slice_locs``: returns the "range" to slice between two labels -- ``get_indexer``: Computes the indexing vector for reindexing / data +* ``slice_locs``: returns the "range" to slice between two labels +* ``get_indexer``: Computes the indexing vector for reindexing / data alignment purposes. See the source / docstrings for more on this -- ``get_indexer_non_unique``: Computes the indexing vector for reindexing / data +* ``get_indexer_non_unique``: Computes the indexing vector for reindexing / data alignment purposes when the index is non-unique. See the source / docstrings for more on this -- ``reindex``: Does any pre-conversion of the input index then calls +* ``reindex``: Does any pre-conversion of the input index then calls ``get_indexer`` -- ``union``, ``intersection``: computes the union or intersection of two +* ``union``, ``intersection``: computes the union or intersection of two Index objects -- ``insert``: Inserts a new label into an Index, yielding a new object -- ``delete``: Delete a label, yielding a new object -- ``drop``: Deletes a set of labels -- ``take``: Analogous to ndarray.take +* ``insert``: Inserts a new label into an Index, yielding a new object +* ``delete``: Delete a label, yielding a new object +* ``drop``: Deletes a set of labels +* ``take``: Analogous to ndarray.take MultiIndex ~~~~~~~~~~ diff --git a/doc/source/io.rst b/doc/source/io.rst index aa2484b0cb5c3..68faefa872c88 100644 --- a/doc/source/io.rst +++ b/doc/source/io.rst @@ -40,14 +40,14 @@ The pandas I/O API is a set of top level ``reader`` functions accessed like :delim: ; text;`CSV `__;:ref:`read_csv`;:ref:`to_csv` - text;`JSON `__;:ref:`read_json`;:ref:`to_json` + text;`JSON `__;:ref:`read_json`;:ref:`to_json` text;`HTML `__;:ref:`read_html`;:ref:`to_html` text; Local clipboard;:ref:`read_clipboard`;:ref:`to_clipboard` binary;`MS Excel `__;:ref:`read_excel`;:ref:`to_excel` binary;`HDF5 Format `__;:ref:`read_hdf`;:ref:`to_hdf` binary;`Feather Format `__;:ref:`read_feather`;:ref:`to_feather` binary;`Parquet Format `__;:ref:`read_parquet`;:ref:`to_parquet` - binary;`Msgpack `__;:ref:`read_msgpack`;:ref:`to_msgpack` + binary;`Msgpack `__;:ref:`read_msgpack`;:ref:`to_msgpack` binary;`Stata `__;:ref:`read_stata`;:ref:`to_stata` binary;`SAS `__;:ref:`read_sas`; binary;`Python Pickle Format `__;:ref:`read_pickle`;:ref:`to_pickle` @@ -66,16 +66,13 @@ The pandas I/O API is a set of top level ``reader`` functions accessed like CSV & Text files ---------------- -The two workhorse functions for reading text files (a.k.a. flat files) are -:func:`read_csv` and :func:`read_table`. They both use the same parsing code to -intelligently convert tabular data into a ``DataFrame`` object. See the -:ref:`cookbook` for some advanced strategies. +The workhorse function for reading text files (a.k.a. flat files) is +:func:`read_csv`. See the :ref:`cookbook` for some advanced strategies. Parsing options ''''''''''''''' -The functions :func:`read_csv` and :func:`read_table` accept the following -common arguments: +:func:`read_csv` accepts the following common arguments: Basic +++++ @@ -116,7 +113,7 @@ header : int or list of ints, default ``'infer'`` existing names. The header can be a list of ints that specify row locations - for a multi-index on the columns e.g. ``[0,1,3]``. Intervening rows + for a MultiIndex on the columns e.g. ``[0,1,3]``. Intervening rows that are not specified will be skipped (e.g. 2 in this example is skipped). Note that this parameter ignores commented lines and empty lines if ``skip_blank_lines=True``, so header=0 denotes the first @@ -252,12 +249,12 @@ Datetime Handling +++++++++++++++++ parse_dates : boolean or list of ints or names or list of lists or dict, default ``False``. - - If ``True`` -> try parsing the index. - - If ``[1, 2, 3]`` -> try parsing columns 1, 2, 3 each as a separate date + * If ``True`` -> try parsing the index. + * If ``[1, 2, 3]`` -> try parsing columns 1, 2, 3 each as a separate date column. - - If ``[[1, 3]]`` -> combine columns 1 and 3 and parse as a single date + * If ``[[1, 3]]`` -> combine columns 1 and 3 and parse as a single date column. - - If ``{'foo': [1, 3]}`` -> parse columns 1, 3 as date and call result 'foo'. + * If ``{'foo': [1, 3]}`` -> parse columns 1, 3 as date and call result 'foo'. A fast-path exists for iso8601-formatted dates. infer_datetime_format : boolean, default ``False`` If ``True`` and parse_dates is enabled for a column, attempt to infer the @@ -298,7 +295,7 @@ compression : {``'infer'``, ``'gzip'``, ``'bz2'``, ``'zip'``, ``'xz'``, ``None`` Set to ``None`` for no decompression. .. versionadded:: 0.18.1 support for 'zip' and 'xz' compression. - + .. versionchanged:: 0.24.0 'infer' option added and set to default. thousands : str, default ``None`` Thousands separator. decimal : str, default ``'.'`` @@ -503,7 +500,7 @@ This matches the behavior of :meth:`Categorical.set_categories`. converted using the :func:`to_numeric` function, or as appropriate, another converter such as :func:`to_datetime`. - When ``dtype`` is a ``CategoricalDtype`` with homogenous ``categories`` ( + When ``dtype`` is a ``CategoricalDtype`` with homogeneous ``categories`` ( all numeric, all datetimes, etc.), the conversion is done automatically. .. ipython:: python @@ -554,7 +551,7 @@ If the header is in a row other than the first, pass the row number to Default behavior is to infer the column names: if no names are passed the behavior is identical to ``header=0`` and column names - are inferred from the first nonblank line of the file, if column + are inferred from the first non-blank line of the file, if column names are passed explicitly then the behavior is identical to ``header=None``. @@ -780,8 +777,8 @@ Date Handling Specifying Date Columns +++++++++++++++++++++++ -To better facilitate working with datetime data, :func:`read_csv` and -:func:`read_table` use the keyword arguments ``parse_dates`` and ``date_parser`` +To better facilitate working with datetime data, :func:`read_csv` +uses the keyword arguments ``parse_dates`` and ``date_parser`` to allow users to specify a variety of columns and date/time formats to turn the input text data into ``datetime`` objects. @@ -868,7 +865,7 @@ data columns: df .. note:: - If a column or index contains an unparseable date, the entire column or + If a column or index contains an unparsable date, the entire column or index will be returned unaltered as an object data type. For non-standard datetime parsing, use :func:`to_datetime` after ``pd.read_csv``. @@ -961,12 +958,12 @@ negative consequences if enabled. Here are some examples of datetime strings that can be guessed (All representing December 30th, 2011 at 00:00:00): -- "20111230" -- "2011/12/30" -- "20111230 00:00:00" -- "12/30/2011 00:00:00" -- "30/Dec/2011 00:00:00" -- "30/December/2011 00:00:00" +* "20111230" +* "2011/12/30" +* "20111230 00:00:00" +* "12/30/2011 00:00:00" +* "30/Dec/2011 00:00:00" +* "30/December/2011 00:00:00" Note that ``infer_datetime_format`` is sensitive to ``dayfirst``. With ``dayfirst=True``, it will guess "01/12/2011" to be December 1st. With @@ -1303,16 +1300,16 @@ with data files that have known and fixed column widths. The function parameters to ``read_fwf`` are largely the same as `read_csv` with two extra parameters, and a different usage of the ``delimiter`` parameter: - - ``colspecs``: A list of pairs (tuples) giving the extents of the - fixed-width fields of each line as half-open intervals (i.e., [from, to[ ). - String value 'infer' can be used to instruct the parser to try detecting - the column specifications from the first 100 rows of the data. Default - behavior, if not specified, is to infer. - - ``widths``: A list of field widths which can be used instead of 'colspecs' - if the intervals are contiguous. - - ``delimiter``: Characters to consider as filler characters in the fixed-width file. - Can be used to specify the filler character of the fields - if it is not spaces (e.g., '~'). +* ``colspecs``: A list of pairs (tuples) giving the extents of the + fixed-width fields of each line as half-open intervals (i.e., [from, to[ ). + String value 'infer' can be used to instruct the parser to try detecting + the column specifications from the first 100 rows of the data. Default + behavior, if not specified, is to infer. +* ``widths``: A list of field widths which can be used instead of 'colspecs' + if the intervals are contiguous. +* ``delimiter``: Characters to consider as filler characters in the fixed-width file. + Can be used to specify the filler character of the fields + if it is not spaces (e.g., '~'). .. ipython:: python :suppress: @@ -1434,7 +1431,7 @@ Suppose you have data indexed by two columns: print(open('data/mindex_ex.csv').read()) -The ``index_col`` argument to ``read_csv`` and ``read_table`` can take a list of +The ``index_col`` argument to ``read_csv`` can take a list of column numbers to turn multiple columns into a ``MultiIndex`` for the index of the returned object: @@ -1505,8 +1502,8 @@ class of the csv module. For this, you have to specify ``sep=None``. .. ipython:: python - print(open('tmp2.sv').read()) - pd.read_csv('tmp2.sv', sep=None, engine='python') + print(open('tmp2.sv').read()) + pd.read_csv('tmp2.sv', sep=None, engine='python') .. _io.multiple_files: @@ -1528,16 +1525,16 @@ rather than reading the entire file into memory, such as the following: .. ipython:: python print(open('tmp.sv').read()) - table = pd.read_table('tmp.sv', sep='|') + table = pd.read_csv('tmp.sv', sep='|') table -By specifying a ``chunksize`` to ``read_csv`` or ``read_table``, the return +By specifying a ``chunksize`` to ``read_csv``, the return value will be an iterable object of type ``TextFileReader``: .. ipython:: python - reader = pd.read_table('tmp.sv', sep='|', chunksize=4) + reader = pd.read_csv('tmp.sv', sep='|', chunksize=4) reader for chunk in reader: @@ -1548,7 +1545,7 @@ Specifying ``iterator=True`` will also return the ``TextFileReader`` object: .. ipython:: python - reader = pd.read_table('tmp.sv', sep='|', iterator=True) + reader = pd.read_csv('tmp.sv', sep='|', iterator=True) reader.get_chunk(5) .. ipython:: python @@ -1566,9 +1563,9 @@ possible pandas uses the C parser (specified as ``engine='c'``), but may fall back to Python if C-unsupported options are specified. Currently, C-unsupported options include: -- ``sep`` other than a single character (e.g. regex separators) -- ``skipfooter`` -- ``sep=None`` with ``delim_whitespace=False`` +* ``sep`` other than a single character (e.g. regex separators) +* ``skipfooter`` +* ``sep=None`` with ``delim_whitespace=False`` Specifying any of the above options will produce a ``ParserWarning`` unless the python engine is selected explicitly using ``engine='python'``. @@ -1602,29 +1599,29 @@ The ``Series`` and ``DataFrame`` objects have an instance method ``to_csv`` whic allows storing the contents of the object as a comma-separated-values file. The function takes a number of arguments. Only the first is required. - - ``path_or_buf``: A string path to the file to write or a StringIO - - ``sep`` : Field delimiter for the output file (default ",") - - ``na_rep``: A string representation of a missing value (default '') - - ``float_format``: Format string for floating point numbers - - ``cols``: Columns to write (default None) - - ``header``: Whether to write out the column names (default True) - - ``index``: whether to write row (index) names (default True) - - ``index_label``: Column label(s) for index column(s) if desired. If None - (default), and `header` and `index` are True, then the index names are - used. (A sequence should be given if the ``DataFrame`` uses MultiIndex). - - ``mode`` : Python write mode, default 'w' - - ``encoding``: a string representing the encoding to use if the contents are - non-ASCII, for Python versions prior to 3 - - ``line_terminator``: Character sequence denoting line end (default '\\n') - - ``quoting``: Set quoting rules as in csv module (default csv.QUOTE_MINIMAL). Note that if you have set a `float_format` then floats are converted to strings and csv.QUOTE_NONNUMERIC will treat them as non-numeric - - ``quotechar``: Character used to quote fields (default '"') - - ``doublequote``: Control quoting of ``quotechar`` in fields (default True) - - ``escapechar``: Character used to escape ``sep`` and ``quotechar`` when - appropriate (default None) - - ``chunksize``: Number of rows to write at a time - - ``tupleize_cols``: If False (default), write as a list of tuples, otherwise - write in an expanded line format suitable for ``read_csv`` - - ``date_format``: Format string for datetime objects +* ``path_or_buf``: A string path to the file to write or a StringIO +* ``sep`` : Field delimiter for the output file (default ",") +* ``na_rep``: A string representation of a missing value (default '') +* ``float_format``: Format string for floating point numbers +* ``columns``: Columns to write (default None) +* ``header``: Whether to write out the column names (default True) +* ``index``: whether to write row (index) names (default True) +* ``index_label``: Column label(s) for index column(s) if desired. If None + (default), and `header` and `index` are True, then the index names are + used. (A sequence should be given if the ``DataFrame`` uses MultiIndex). +* ``mode`` : Python write mode, default 'w' +* ``encoding``: a string representing the encoding to use if the contents are + non-ASCII, for Python versions prior to 3 +* ``line_terminator``: Character sequence denoting line end (default '\\n') +* ``quoting``: Set quoting rules as in csv module (default csv.QUOTE_MINIMAL). Note that if you have set a `float_format` then floats are converted to strings and csv.QUOTE_NONNUMERIC will treat them as non-numeric +* ``quotechar``: Character used to quote fields (default '"') +* ``doublequote``: Control quoting of ``quotechar`` in fields (default True) +* ``escapechar``: Character used to escape ``sep`` and ``quotechar`` when + appropriate (default None) +* ``chunksize``: Number of rows to write at a time +* ``tupleize_cols``: If False (default), write as a list of tuples, otherwise + write in an expanded line format suitable for ``read_csv`` +* ``date_format``: Format string for datetime objects Writing a formatted string ++++++++++++++++++++++++++ @@ -1634,22 +1631,22 @@ Writing a formatted string The ``DataFrame`` object has an instance method ``to_string`` which allows control over the string representation of the object. All arguments are optional: - - ``buf`` default None, for example a StringIO object - - ``columns`` default None, which columns to write - - ``col_space`` default None, minimum width of each column. - - ``na_rep`` default ``NaN``, representation of NA value - - ``formatters`` default None, a dictionary (by column) of functions each of - which takes a single argument and returns a formatted string - - ``float_format`` default None, a function which takes a single (float) - argument and returns a formatted string; to be applied to floats in the - ``DataFrame``. - - ``sparsify`` default True, set to False for a ``DataFrame`` with a hierarchical - index to print every multiindex key at each row. - - ``index_names`` default True, will print the names of the indices - - ``index`` default True, will print the index (ie, row labels) - - ``header`` default True, will print the column labels - - ``justify`` default ``left``, will print column headers left- or - right-justified +* ``buf`` default None, for example a StringIO object +* ``columns`` default None, which columns to write +* ``col_space`` default None, minimum width of each column. +* ``na_rep`` default ``NaN``, representation of NA value +* ``formatters`` default None, a dictionary (by column) of functions each of + which takes a single argument and returns a formatted string +* ``float_format`` default None, a function which takes a single (float) + argument and returns a formatted string; to be applied to floats in the + ``DataFrame``. +* ``sparsify`` default True, set to False for a ``DataFrame`` with a hierarchical + index to print every MultiIndex key at each row. +* ``index_names`` default True, will print the names of the indices +* ``index`` default True, will print the index (ie, row labels) +* ``header`` default True, will print the column labels +* ``justify`` default ``left``, will print column headers left- or + right-justified The ``Series`` object also has a ``to_string`` method, but with only the ``buf``, ``na_rep``, ``float_format`` arguments. There is also a ``length`` argument @@ -1670,17 +1667,17 @@ Writing JSON A ``Series`` or ``DataFrame`` can be converted to a valid JSON string. Use ``to_json`` with optional parameters: -- ``path_or_buf`` : the pathname or buffer to write the output +* ``path_or_buf`` : the pathname or buffer to write the output This can be ``None`` in which case a JSON string is returned -- ``orient`` : +* ``orient`` : ``Series``: - - default is ``index`` - - allowed values are {``split``, ``records``, ``index``} + * default is ``index`` + * allowed values are {``split``, ``records``, ``index``} ``DataFrame``: - - default is ``columns`` - - allowed values are {``split``, ``records``, ``index``, ``columns``, ``values``, ``table``} + * default is ``columns`` + * allowed values are {``split``, ``records``, ``index``, ``columns``, ``values``, ``table``} The format of the JSON string @@ -1694,12 +1691,12 @@ with optional parameters: ``columns``; dict like {column -> {index -> value}} ``values``; just the values array -- ``date_format`` : string, type of date conversion, 'epoch' for timestamp, 'iso' for ISO8601. -- ``double_precision`` : The number of decimal places to use when encoding floating point values, default 10. -- ``force_ascii`` : force encoded string to be ASCII, default True. -- ``date_unit`` : The time unit to encode to, governs timestamp and ISO8601 precision. One of 's', 'ms', 'us' or 'ns' for seconds, milliseconds, microseconds and nanoseconds respectively. Default 'ms'. -- ``default_handler`` : The handler to call if an object cannot otherwise be converted to a suitable format for JSON. Takes a single argument, which is the object to convert, and returns a serializable object. -- ``lines`` : If ``records`` orient, then will write each record per line as json. +* ``date_format`` : string, type of date conversion, 'epoch' for timestamp, 'iso' for ISO8601. +* ``double_precision`` : The number of decimal places to use when encoding floating point values, default 10. +* ``force_ascii`` : force encoded string to be ASCII, default True. +* ``date_unit`` : The time unit to encode to, governs timestamp and ISO8601 precision. One of 's', 'ms', 'us' or 'ns' for seconds, milliseconds, microseconds and nanoseconds respectively. Default 'ms'. +* ``default_handler`` : The handler to call if an object cannot otherwise be converted to a suitable format for JSON. Takes a single argument, which is the object to convert, and returns a serializable object. +* ``lines`` : If ``records`` orient, then will write each record per line as json. Note ``NaN``'s, ``NaT``'s and ``None`` will be converted to ``null`` and ``datetime`` objects will be converted based on the ``date_format`` and ``date_unit`` parameters. @@ -1818,19 +1815,19 @@ Fallback Behavior If the JSON serializer cannot handle the container contents directly it will fall back in the following manner: -- if the dtype is unsupported (e.g. ``np.complex``) then the ``default_handler``, if provided, will be called +* if the dtype is unsupported (e.g. ``np.complex``) then the ``default_handler``, if provided, will be called for each value, otherwise an exception is raised. -- if an object is unsupported it will attempt the following: +* if an object is unsupported it will attempt the following: - * check if the object has defined a ``toDict`` method and call it. - A ``toDict`` method should return a ``dict`` which will then be JSON serialized. + * check if the object has defined a ``toDict`` method and call it. + A ``toDict`` method should return a ``dict`` which will then be JSON serialized. - * invoke the ``default_handler`` if one was provided. + * invoke the ``default_handler`` if one was provided. - * convert the object to a ``dict`` by traversing its contents. However this will often fail - with an ``OverflowError`` or give unexpected results. + * convert the object to a ``dict`` by traversing its contents. However this will often fail + with an ``OverflowError`` or give unexpected results. In general the best approach for unsupported objects or dtypes is to provide a ``default_handler``. For example: @@ -1856,20 +1853,20 @@ Reading a JSON string to pandas object can take a number of parameters. The parser will try to parse a ``DataFrame`` if ``typ`` is not supplied or is ``None``. To explicitly force ``Series`` parsing, pass ``typ=series`` -- ``filepath_or_buffer`` : a **VALID** JSON string or file handle / StringIO. The string could be +* ``filepath_or_buffer`` : a **VALID** JSON string or file handle / StringIO. The string could be a URL. Valid URL schemes include http, ftp, S3, and file. For file URLs, a host is expected. For instance, a local file could be file ://localhost/path/to/table.json -- ``typ`` : type of object to recover (series or frame), default 'frame' -- ``orient`` : +* ``typ`` : type of object to recover (series or frame), default 'frame' +* ``orient`` : Series : - - default is ``index`` - - allowed values are {``split``, ``records``, ``index``} + * default is ``index`` + * allowed values are {``split``, ``records``, ``index``} DataFrame - - default is ``columns`` - - allowed values are {``split``, ``records``, ``index``, ``columns``, ``values``, ``table``} + * default is ``columns`` + * allowed values are {``split``, ``records``, ``index``, ``columns``, ``values``, ``table``} The format of the JSON string @@ -1885,20 +1882,20 @@ is ``None``. To explicitly force ``Series`` parsing, pass ``typ=series`` ``table``; adhering to the JSON `Table Schema`_ -- ``dtype`` : if True, infer dtypes, if a dict of column to dtype, then use those, if ``False``, then don't infer dtypes at all, default is True, apply only to the data. -- ``convert_axes`` : boolean, try to convert the axes to the proper dtypes, default is ``True`` -- ``convert_dates`` : a list of columns to parse for dates; If ``True``, then try to parse date-like columns, default is ``True``. -- ``keep_default_dates`` : boolean, default ``True``. If parsing dates, then parse the default date-like columns. -- ``numpy`` : direct decoding to NumPy arrays. default is ``False``; +* ``dtype`` : if True, infer dtypes, if a dict of column to dtype, then use those, if ``False``, then don't infer dtypes at all, default is True, apply only to the data. +* ``convert_axes`` : boolean, try to convert the axes to the proper dtypes, default is ``True`` +* ``convert_dates`` : a list of columns to parse for dates; If ``True``, then try to parse date-like columns, default is ``True``. +* ``keep_default_dates`` : boolean, default ``True``. If parsing dates, then parse the default date-like columns. +* ``numpy`` : direct decoding to NumPy arrays. default is ``False``; Supports numeric data only, although labels may be non-numeric. Also note that the JSON ordering **MUST** be the same for each term if ``numpy=True``. -- ``precise_float`` : boolean, default ``False``. Set to enable usage of higher precision (strtod) function when decoding string to double values. Default (``False``) is to use fast but less precise builtin functionality. -- ``date_unit`` : string, the timestamp unit to detect if converting dates. Default +* ``precise_float`` : boolean, default ``False``. Set to enable usage of higher precision (strtod) function when decoding string to double values. Default (``False``) is to use fast but less precise builtin functionality. +* ``date_unit`` : string, the timestamp unit to detect if converting dates. Default None. By default the timestamp precision will be detected, if this is not desired then pass one of 's', 'ms', 'us' or 'ns' to force timestamp precision to seconds, milliseconds, microseconds or nanoseconds respectively. -- ``lines`` : reads file as one json object per line. -- ``encoding`` : The encoding to use to decode py3 bytes. -- ``chunksize`` : when used in combination with ``lines=True``, return a JsonReader which reads in ``chunksize`` lines per iteration. +* ``lines`` : reads file as one json object per line. +* ``encoding`` : The encoding to use to decode py3 bytes. +* ``chunksize`` : when used in combination with ``lines=True``, return a JsonReader which reads in ``chunksize`` lines per iteration. The parser will raise one of ``ValueError/TypeError/AssertionError`` if the JSON is not parseable. @@ -2175,10 +2172,10 @@ object str A few notes on the generated table schema: -- The ``schema`` object contains a ``pandas_version`` field. This contains +* The ``schema`` object contains a ``pandas_version`` field. This contains the version of pandas' dialect of the schema, and will be incremented with each revision. -- All dates are converted to UTC when serializing. Even timezone naïve values, +* All dates are converted to UTC when serializing. Even timezone naive values, which are treated as UTC with an offset of 0. .. ipython:: python @@ -2187,7 +2184,7 @@ A few notes on the generated table schema: s = pd.Series(pd.date_range('2016', periods=4)) build_table_schema(s) -- datetimes with a timezone (before serializing), include an additional field +* datetimes with a timezone (before serializing), include an additional field ``tz`` with the time zone name (e.g. ``'US/Central'``). .. ipython:: python @@ -2196,7 +2193,7 @@ A few notes on the generated table schema: tz='US/Central')) build_table_schema(s_tz) -- Periods are converted to timestamps before serialization, and so have the +* Periods are converted to timestamps before serialization, and so have the same behavior of being converted to UTC. In addition, periods will contain and additional field ``freq`` with the period's frequency, e.g. ``'A-DEC'``. @@ -2206,7 +2203,7 @@ A few notes on the generated table schema: periods=4)) build_table_schema(s_per) -- Categoricals use the ``any`` type and an ``enum`` constraint listing +* Categoricals use the ``any`` type and an ``enum`` constraint listing the set of possible values. Additionally, an ``ordered`` field is included: .. ipython:: python @@ -2214,7 +2211,7 @@ A few notes on the generated table schema: s_cat = pd.Series(pd.Categorical(['a', 'b', 'a'])) build_table_schema(s_cat) -- A ``primaryKey`` field, containing an array of labels, is included +* A ``primaryKey`` field, containing an array of labels, is included *if the index is unique*: .. ipython:: python @@ -2222,7 +2219,7 @@ A few notes on the generated table schema: s_dupe = pd.Series([1, 2], index=[1, 1]) build_table_schema(s_dupe) -- The ``primaryKey`` behavior is the same with MultiIndexes, but in this +* The ``primaryKey`` behavior is the same with MultiIndexes, but in this case the ``primaryKey`` is an array: .. ipython:: python @@ -2231,21 +2228,21 @@ A few notes on the generated table schema: (0, 1)])) build_table_schema(s_multi) -- The default naming roughly follows these rules: +* The default naming roughly follows these rules: - + For series, the ``object.name`` is used. If that's none, then the - name is ``values`` - + For ``DataFrames``, the stringified version of the column name is used - + For ``Index`` (not ``MultiIndex``), ``index.name`` is used, with a - fallback to ``index`` if that is None. - + For ``MultiIndex``, ``mi.names`` is used. If any level has no name, - then ``level_`` is used. + * For series, the ``object.name`` is used. If that's none, then the + name is ``values`` + * For ``DataFrames``, the stringified version of the column name is used + * For ``Index`` (not ``MultiIndex``), ``index.name`` is used, with a + fallback to ``index`` if that is None. + * For ``MultiIndex``, ``mi.names`` is used. If any level has no name, + then ``level_`` is used. .. versionadded:: 0.23.0 ``read_json`` also accepts ``orient='table'`` as an argument. This allows for -the preserveration of metadata such as dtypes and index names in a +the preservation of metadata such as dtypes and index names in a round-trippable manner. .. ipython:: python @@ -2276,7 +2273,7 @@ indicate missing values and the subsequent read cannot distinguish the intent. new_df = pd.read_json('test.json', orient='table') print(new_df.index.name) -.. _Table Schema: http://specs.frictionlessdata.io/json-table-schema/ +.. _Table Schema: https://specs.frictionlessdata.io/json-table-schema/ HTML ---- @@ -2304,7 +2301,7 @@ Read a URL with no options: .. ipython:: python - url = 'http://www.fdic.gov/bank/individual/failed/banklist.html' + url = 'https://www.fdic.gov/bank/individual/failed/banklist.html' dfs = pd.read_html(url) dfs @@ -2344,7 +2341,7 @@ You can even pass in an instance of ``StringIO`` if you so desire: that having so many network-accessing functions slows down the documentation build. If you spot an error or an example that doesn't run, please do not hesitate to report it over on `pandas GitHub issues page - `__. + `__. Read a URL and match a table that contains specific text: @@ -2356,7 +2353,7 @@ Read a URL and match a table that contains specific text: Specify a header row (by default ```` or ```` elements located within a ```` are used to form the column index, if multiple rows are contained within -```` then a multiindex is created); if specified, the header row is taken +```` then a MultiIndex is created); if specified, the header row is taken from the data minus the parsed header elements (```` elements). .. code-block:: python @@ -2601,68 +2598,68 @@ parse HTML tables in the top-level pandas io function ``read_html``. **Issues with** |lxml|_ - * Benefits +* Benefits - * |lxml|_ is very fast. + * |lxml|_ is very fast. - * |lxml|_ requires Cython to install correctly. + * |lxml|_ requires Cython to install correctly. - * Drawbacks +* Drawbacks - * |lxml|_ does *not* make any guarantees about the results of its parse - *unless* it is given |svm|_. + * |lxml|_ does *not* make any guarantees about the results of its parse + *unless* it is given |svm|_. - * In light of the above, we have chosen to allow you, the user, to use the - |lxml|_ backend, but **this backend will use** |html5lib|_ if |lxml|_ - fails to parse + * In light of the above, we have chosen to allow you, the user, to use the + |lxml|_ backend, but **this backend will use** |html5lib|_ if |lxml|_ + fails to parse - * It is therefore *highly recommended* that you install both - |BeautifulSoup4|_ and |html5lib|_, so that you will still get a valid - result (provided everything else is valid) even if |lxml|_ fails. + * It is therefore *highly recommended* that you install both + |BeautifulSoup4|_ and |html5lib|_, so that you will still get a valid + result (provided everything else is valid) even if |lxml|_ fails. **Issues with** |BeautifulSoup4|_ **using** |lxml|_ **as a backend** - * The above issues hold here as well since |BeautifulSoup4|_ is essentially - just a wrapper around a parser backend. +* The above issues hold here as well since |BeautifulSoup4|_ is essentially + just a wrapper around a parser backend. **Issues with** |BeautifulSoup4|_ **using** |html5lib|_ **as a backend** - * Benefits +* Benefits - * |html5lib|_ is far more lenient than |lxml|_ and consequently deals - with *real-life markup* in a much saner way rather than just, e.g., - dropping an element without notifying you. + * |html5lib|_ is far more lenient than |lxml|_ and consequently deals + with *real-life markup* in a much saner way rather than just, e.g., + dropping an element without notifying you. - * |html5lib|_ *generates valid HTML5 markup from invalid markup - automatically*. This is extremely important for parsing HTML tables, - since it guarantees a valid document. However, that does NOT mean that - it is "correct", since the process of fixing markup does not have a - single definition. + * |html5lib|_ *generates valid HTML5 markup from invalid markup + automatically*. This is extremely important for parsing HTML tables, + since it guarantees a valid document. However, that does NOT mean that + it is "correct", since the process of fixing markup does not have a + single definition. - * |html5lib|_ is pure Python and requires no additional build steps beyond - its own installation. + * |html5lib|_ is pure Python and requires no additional build steps beyond + its own installation. - * Drawbacks +* Drawbacks - * The biggest drawback to using |html5lib|_ is that it is slow as - molasses. However consider the fact that many tables on the web are not - big enough for the parsing algorithm runtime to matter. It is more - likely that the bottleneck will be in the process of reading the raw - text from the URL over the web, i.e., IO (input-output). For very large - tables, this might not be true. + * The biggest drawback to using |html5lib|_ is that it is slow as + molasses. However consider the fact that many tables on the web are not + big enough for the parsing algorithm runtime to matter. It is more + likely that the bottleneck will be in the process of reading the raw + text from the URL over the web, i.e., IO (input-output). For very large + tables, this might not be true. .. |svm| replace:: **strictly valid markup** -.. _svm: http://validator.w3.org/docs/help.html#validation_basics +.. _svm: https://validator.w3.org/docs/help.html#validation_basics .. |html5lib| replace:: **html5lib** .. _html5lib: https://github.com/html5lib/html5lib-python .. |BeautifulSoup4| replace:: **BeautifulSoup4** -.. _BeautifulSoup4: http://www.crummy.com/software/BeautifulSoup +.. _BeautifulSoup4: https://www.crummy.com/software/BeautifulSoup .. |lxml| replace:: **lxml** -.. _lxml: http://lxml.de +.. _lxml: https://lxml.de @@ -2753,13 +2750,13 @@ Specifying Sheets .. note :: An ExcelFile's attribute ``sheet_names`` provides access to a list of sheets. -- The arguments ``sheet_name`` allows specifying the sheet or sheets to read. -- The default value for ``sheet_name`` is 0, indicating to read the first sheet -- Pass a string to refer to the name of a particular sheet in the workbook. -- Pass an integer to refer to the index of a sheet. Indices follow Python +* The arguments ``sheet_name`` allows specifying the sheet or sheets to read. +* The default value for ``sheet_name`` is 0, indicating to read the first sheet +* Pass a string to refer to the name of a particular sheet in the workbook. +* Pass an integer to refer to the index of a sheet. Indices follow Python convention, beginning at 0. -- Pass a list of either strings or integers, to return a dictionary of specified sheets. -- Pass a ``None`` to return a dictionary of all available sheets. +* Pass a list of either strings or integers, to return a dictionary of specified sheets. +* Pass a ``None`` to return a dictionary of all available sheets. .. code-block:: python @@ -3030,9 +3027,9 @@ files if `Xlsxwriter`_ is not available. To specify which writer you want to use, you can pass an engine keyword argument to ``to_excel`` and to ``ExcelWriter``. The built-in engines are: -- ``openpyxl``: version 2.4 or higher is required -- ``xlsxwriter`` -- ``xlwt`` +* ``openpyxl``: version 2.4 or higher is required +* ``xlsxwriter`` +* ``xlwt`` .. code-block:: python @@ -3055,8 +3052,8 @@ Style and Formatting The look and feel of Excel worksheets created from pandas can be modified using the following parameters on the ``DataFrame``'s ``to_excel`` method. -- ``float_format`` : Format string for floating point numbers (default ``None``). -- ``freeze_panes`` : A tuple of two integers representing the bottommost row and rightmost column to freeze. Each of these parameters is one-based, so (1, 1) will freeze the first row and first column (default ``None``). +* ``float_format`` : Format string for floating point numbers (default ``None``). +* ``freeze_panes`` : A tuple of two integers representing the bottommost row and rightmost column to freeze. Each of these parameters is one-based, so (1, 1) will freeze the first row and first column (default ``None``). @@ -3067,7 +3064,7 @@ Clipboard A handy way to grab data is to use the :meth:`~DataFrame.read_clipboard` method, which takes the contents of the clipboard buffer and passes them to the -``read_table`` method. For instance, you can copy the following text to the +``read_csv`` method. For instance, you can copy the following text to the clipboard (CTRL-C on many operating systems): .. code-block:: python @@ -3141,15 +3138,15 @@ any pickled pandas object (or any other pickled object) from file: .. warning:: - Several internal refactorings have been done while still preserving + Several internal refactoring have been done while still preserving compatibility with pickles created with older versions of pandas. However, for such cases, pickled ``DataFrames``, ``Series`` etc, must be read with ``pd.read_pickle``, rather than ``pickle.load``. - See `here `__ - and `here `__ + See `here `__ + and `here `__ for some examples of compatibility-breaking changes. See - `this question `__ + `this question `__ for a detailed explanation. .. _io.pickle.compression: @@ -3297,7 +3294,7 @@ HDF5 (PyTables) ``HDFStore`` is a dict-like object which reads and writes pandas using the high performance HDF5 format using the excellent `PyTables -`__ library. See the :ref:`cookbook ` +`__ library. See the :ref:`cookbook ` for some advanced strategies .. warning:: @@ -3554,6 +3551,25 @@ everything in the sub-store and **below**, so be *careful*. store.remove('food') store + +You can walk through the group hierarchy using the ``walk`` method which +will yield a tuple for each group key along with the relative keys of its contents. + +.. versionadded:: 0.24.0 + + +.. ipython:: python + + for (path, subgroups, subkeys) in store.walk(): + for subgroup in subgroups: + print('GROUP: {}/{}'.format(path, subgroup)) + for subkey in subkeys: + key = '/'.join([path, subkey]) + print('KEY: {}'.format(key)) + print(store.get(key)) + + + .. warning:: Hierarchical keys cannot be retrieved as dotted (attribute) access as described above for items stored under the root node. @@ -3615,10 +3631,10 @@ defaults to `nan`. # we have provided a minimum string column size store.root.df_mixed.table -Storing Multi-Index DataFrames -++++++++++++++++++++++++++++++ +Storing MultiIndex DataFrames ++++++++++++++++++++++++++++++ -Storing multi-index ``DataFrames`` as tables is very similar to +Storing MultiIndex ``DataFrames`` as tables is very similar to storing/selecting from homogeneous index ``DataFrames``. .. ipython:: python @@ -3654,10 +3670,10 @@ data. A query is specified using the ``Term`` class under the hood, as a boolean expression. -- ``index`` and ``columns`` are supported indexers of a ``DataFrames``. -- ``major_axis``, ``minor_axis``, and ``items`` are supported indexers of +* ``index`` and ``columns`` are supported indexers of a ``DataFrames``. +* ``major_axis``, ``minor_axis``, and ``items`` are supported indexers of the Panel. -- if ``data_columns`` are specified, these can be used as additional indexers. +* if ``data_columns`` are specified, these can be used as additional indexers. Valid comparison operators are: @@ -3665,9 +3681,9 @@ Valid comparison operators are: Valid boolean expressions are combined with: -- ``|`` : or -- ``&`` : and -- ``(`` and ``)`` : for grouping +* ``|`` : or +* ``&`` : and +* ``(`` and ``)`` : for grouping These rules are similar to how boolean expressions are used in pandas for indexing. @@ -3680,16 +3696,16 @@ These rules are similar to how boolean expressions are used in pandas for indexi The following are valid expressions: -- ``'index >= date'`` -- ``"columns = ['A', 'D']"`` -- ``"columns in ['A', 'D']"`` -- ``'columns = A'`` -- ``'columns == A'`` -- ``"~(columns = ['A', 'B'])"`` -- ``'index > df.index[3] & string = "bar"'`` -- ``'(index > df.index[3] & index <= df.index[6]) | string = "bar"'`` -- ``"ts >= Timestamp('2012-02-01')"`` -- ``"major_axis>=20130101"`` +* ``'index >= date'`` +* ``"columns = ['A', 'D']"`` +* ``"columns in ['A', 'D']"`` +* ``'columns = A'`` +* ``'columns == A'`` +* ``"~(columns = ['A', 'B'])"`` +* ``'index > df.index[3] & string = "bar"'`` +* ``'(index > df.index[3] & index <= df.index[6]) | string = "bar"'`` +* ``"ts >= Timestamp('2012-02-01')"`` +* ``"major_axis>=20130101"`` The ``indexers`` are on the left-hand side of the sub-expression: @@ -3697,11 +3713,11 @@ The ``indexers`` are on the left-hand side of the sub-expression: The right-hand side of the sub-expression (after a comparison operator) can be: -- functions that will be evaluated, e.g. ``Timestamp('2012-02-01')`` -- strings, e.g. ``"bar"`` -- date-like, e.g. ``20130101``, or ``"20130101"`` -- lists, e.g. ``"['A', 'B']"`` -- variables that are defined in the local names space, e.g. ``date`` +* functions that will be evaluated, e.g. ``Timestamp('2012-02-01')`` +* strings, e.g. ``"bar"`` +* date-like, e.g. ``20130101``, or ``"20130101"`` +* lists, e.g. ``"['A', 'B']"`` +* variables that are defined in the local names space, e.g. ``date`` .. note:: @@ -3862,7 +3878,7 @@ Then create the index when finished appending. os.remove('appends.h5') -See `here `__ for how to create a completely-sorted-index (CSI) on an existing store. +See `here `__ for how to create a completely-sorted-index (CSI) on an existing store. .. _io.hdf5-query-data-columns: @@ -4080,15 +4096,15 @@ simple use case. You store panel-type data, with dates in the ``major_axis`` and ids in the ``minor_axis``. The data is then interleaved like this: -- date_1 - - id_1 - - id_2 - - . - - id_n -- date_2 - - id_1 - - . - - id_n +* date_1 + * id_1 + * id_2 + * . + * id_n +* date_2 + * id_1 + * . + * id_n It should be clear that a delete operation on the ``major_axis`` will be fairly quick, as one chunk is removed, then the following data moved. On @@ -4135,8 +4151,8 @@ control compression: ``complevel`` and ``complib``. compression to choose depends on your specific needs and data. The list of supported compression libraries: - - `zlib `_: The default compression library. A classic in terms of compression, achieves good compression rates but is somewhat slow. - - `lzo `_: Fast compression and decompression. + - `zlib `_: The default compression library. A classic in terms of compression, achieves good compression rates but is somewhat slow. + - `lzo `_: Fast compression and decompression. - `bzip2 `_: Good compression rates. - `blosc `_: Fast compression and decompression. @@ -4155,7 +4171,7 @@ control compression: ``complevel`` and ``complib``. compression ratios at the expense of speed. - `blosc:snappy `_: A popular compressor used in many places. - - `blosc:zlib `_: A classic; + - `blosc:zlib `_: A classic; somewhat slower than the previous ones, but achieving better compression ratios. - `blosc:zstd `_: An @@ -4216,12 +4232,12 @@ Caveats need to serialize these operations in a single thread in a single process. You will corrupt your data otherwise. See the (:issue:`2397`) for more information. -- If you use locks to manage write access between multiple processes, you +* If you use locks to manage write access between multiple processes, you may want to use :py:func:`~os.fsync` before releasing write locks. For convenience you can use ``store.flush(fsync=True)`` to do this for you. -- Once a ``table`` is created its items (Panel) / columns (DataFrame) +* Once a ``table`` is created its items (Panel) / columns (DataFrame) are fixed; only exactly the same columns can be appended -- Be aware that timezones (e.g., ``pytz.timezone('US/Eastern')``) +* Be aware that timezones (e.g., ``pytz.timezone('US/Eastern')``) are not necessarily equal across timezone versions. So if data is localized to a specific timezone in the HDFStore using one version of a timezone library and that data is updated with another version, the data @@ -4356,7 +4372,7 @@ tables. It is possible to write an ``HDFStore`` object that can easily be imported into ``R`` using the ``rhdf5`` library (`Package website`_). Create a table format store like this: -.. _package website: http://www.bioconductor.org/packages/release/bioc/html/rhdf5.html +.. _package website: https://www.bioconductor.org/packages/release/bioc/html/rhdf5.html .. ipython:: python @@ -4438,24 +4454,24 @@ Now you can import the ``DataFrame`` into R: Performance ''''''''''' -- ``tables`` format come with a writing performance penalty as compared to +* ``tables`` format come with a writing performance penalty as compared to ``fixed`` stores. The benefit is the ability to append/delete and query (potentially very large amounts of data). Write times are generally longer as compared with regular stores. Query times can be quite fast, especially on an indexed axis. -- You can pass ``chunksize=`` to ``append``, specifying the +* You can pass ``chunksize=`` to ``append``, specifying the write chunksize (default is 50000). This will significantly lower your memory usage on writing. -- You can pass ``expectedrows=`` to the first ``append``, +* You can pass ``expectedrows=`` to the first ``append``, to set the TOTAL number of expected rows that ``PyTables`` will expected. This will optimize read/write performance. -- Duplicate rows can be written to tables, but are filtered out in +* Duplicate rows can be written to tables, but are filtered out in selection (with the last items being selected; thus a table is unique on major, minor pairs) -- A ``PerformanceWarning`` will be raised if you are attempting to +* A ``PerformanceWarning`` will be raised if you are attempting to store types that will be pickled by PyTables (rather than stored as endemic types). See - `Here `__ + `Here `__ for more information and some solutions. @@ -4482,14 +4498,14 @@ dtypes, including extension dtypes such as categorical and datetime with tz. Several caveats. -- This is a newer library, and the format, though stable, is not guaranteed to be backward compatible +* This is a newer library, and the format, though stable, is not guaranteed to be backward compatible to the earlier versions. -- The format will NOT write an ``Index``, or ``MultiIndex`` for the +* The format will NOT write an ``Index``, or ``MultiIndex`` for the ``DataFrame`` and will raise an error if a non-default one is provided. You can ``.reset_index()`` to store the index or ``.reset_index(drop=True)`` to ignore it. -- Duplicate column names and non-string columns names are not supported -- Non supported types include ``Period`` and actual Python object types. These will raise a helpful error message +* Duplicate column names and non-string columns names are not supported +* Non supported types include ``Period`` and actual Python object types. These will raise a helpful error message on an attempt at serialization. See the `Full Documentation `__. @@ -4550,17 +4566,20 @@ dtypes, including extension dtypes such as datetime with tz. Several caveats. -- Duplicate column names and non-string columns names are not supported. -- Index level names, if specified, must be strings. -- Categorical dtypes can be serialized to parquet, but will de-serialize as ``object`` dtype. -- Non supported types include ``Period`` and actual Python object types. These will raise a helpful error message +* Duplicate column names and non-string columns names are not supported. +* The ``pyarrow`` engine always writes the index to the output, but ``fastparquet`` only writes non-default + indexes. This extra column can cause problems for non-Pandas consumers that are not expecting it. You can + force including or omitting indexes with the ``index`` argument, regardless of the underlying engine. +* Index level names, if specified, must be strings. +* Categorical dtypes can be serialized to parquet, but will de-serialize as ``object`` dtype. +* Non supported types include ``Period`` and actual Python object types. These will raise a helpful error message on an attempt at serialization. You can specify an ``engine`` to direct the serialization. This can be one of ``pyarrow``, or ``fastparquet``, or ``auto``. If the engine is NOT specified, then the ``pd.options.io.parquet.engine`` option is checked; if this is also ``auto``, then ``pyarrow`` is tried, and falling back to ``fastparquet``. -See the documentation for `pyarrow `__ and `fastparquet `__. +See the documentation for `pyarrow `__ and `fastparquet `__. .. note:: @@ -4614,6 +4633,41 @@ Read only certain columns of a parquet file. os.remove('example_pa.parquet') os.remove('example_fp.parquet') + +Handling Indexes +'''''''''''''''' + +Serializing a ``DataFrame`` to parquet may include the implicit index as one or +more columns in the output file. Thus, this code: + +.. ipython:: python + + df = pd.DataFrame({'a': [1, 2], 'b': [3, 4]}) + df.to_parquet('test.parquet', engine='pyarrow') + +creates a parquet file with *three* columns if you use ``pyarrow`` for serialization: +``a``, ``b``, and ``__index_level_0__``. If you're using ``fastparquet``, the +index `may or may not `_ +be written to the file. + +This unexpected extra column causes some databases like Amazon Redshift to reject +the file, because that column doesn't exist in the target table. + +If you want to omit a dataframe's indexes when writing, pass ``index=False`` to +:func:`~pandas.DataFrame.to_parquet`: + +.. ipython:: python + + df.to_parquet('test.parquet', index=False) + +This creates a parquet file with just the two expected columns, ``a`` and ``b``. +If your ``DataFrame`` has a custom index, you won't get it back when you load +this file into a ``DataFrame``. + +Passing ``index=True`` will *always* write the index, even if that's not the +underlying engine's default behavior. + + .. _io.sql: SQL Queries @@ -4627,13 +4681,13 @@ for PostgreSQL or `pymysql `__ for MySQL. For `SQLite `__ this is included in Python's standard library by default. You can find an overview of supported drivers for each SQL dialect in the -`SQLAlchemy docs `__. +`SQLAlchemy docs `__. If SQLAlchemy is not installed, a fallback is only provided for sqlite (and for mysql for backwards compatibility, but this is deprecated and will be removed in a future version). This mode requires a Python database adapter which respect the `Python -DB-API `__. +DB-API `__. See also some :ref:`cookbook examples ` for some advanced strategies. @@ -4655,7 +4709,7 @@ The key functions are: the provided input (database table name or sql query). Table names do not need to be quoted if they have special characters. -In the following example, we use the `SQlite `__ SQL database +In the following example, we use the `SQlite `__ SQL database engine. You can use a temporary SQLite database where data are stored in "memory". @@ -4663,7 +4717,7 @@ To connect with SQLAlchemy you use the :func:`create_engine` function to create object from database URI. You only need to create the engine once per database you are connecting to. For more information on :func:`create_engine` and the URI formatting, see the examples -below and the SQLAlchemy `documentation `__ +below and the SQLAlchemy `documentation `__ .. ipython:: python @@ -4719,12 +4773,6 @@ writes ``data`` to the database in batches of 1000 rows at a time: data.to_sql('data_chunked', engine, chunksize=1000) -.. note:: - - The function :func:`~pandas.DataFrame.to_sql` will perform a multivalue - insert if the engine dialect ``supports_multivalues_insert``. This will - greatly speed up the insert in some cases. - SQL data types ++++++++++++++ @@ -4882,7 +4930,7 @@ connecting to. # or absolute, starting with a slash: engine = create_engine('sqlite:////absolute/path/to/foo.db') -For more information see the examples the SQLAlchemy `documentation `__ +For more information see the examples the SQLAlchemy `documentation `__ Advanced SQLAlchemy queries @@ -4927,7 +4975,7 @@ Sqlite fallback The use of sqlite is supported without using SQLAlchemy. This mode requires a Python database adapter which respect the `Python -DB-API `__. +DB-API `__. You can create connections like so: @@ -5185,7 +5233,7 @@ xarray_ provides data structures inspired by the pandas ``DataFrame`` for workin with multi-dimensional datasets, with a focus on the netCDF file format and easy conversion to and from pandas. -.. _xarray: http://xarray.pydata.org/ +.. _xarray: https://xarray.pydata.org/ .. _io.perf: diff --git a/doc/source/merging.rst b/doc/source/merging.rst index 1161656731f88..98914c13d4d31 100644 --- a/doc/source/merging.rst +++ b/doc/source/merging.rst @@ -81,33 +81,33 @@ some configurable handling of "what to do with the other axes": keys=None, levels=None, names=None, verify_integrity=False, copy=True) -- ``objs`` : a sequence or mapping of Series, DataFrame, or Panel objects. If a +* ``objs`` : a sequence or mapping of Series, DataFrame, or Panel objects. If a dict is passed, the sorted keys will be used as the `keys` argument, unless it is passed, in which case the values will be selected (see below). Any None objects will be dropped silently unless they are all None in which case a ValueError will be raised. -- ``axis`` : {0, 1, ...}, default 0. The axis to concatenate along. -- ``join`` : {'inner', 'outer'}, default 'outer'. How to handle indexes on +* ``axis`` : {0, 1, ...}, default 0. The axis to concatenate along. +* ``join`` : {'inner', 'outer'}, default 'outer'. How to handle indexes on other axis(es). Outer for union and inner for intersection. -- ``ignore_index`` : boolean, default False. If True, do not use the index +* ``ignore_index`` : boolean, default False. If True, do not use the index values on the concatenation axis. The resulting axis will be labeled 0, ..., n - 1. This is useful if you are concatenating objects where the concatenation axis does not have meaningful indexing information. Note the index values on the other axes are still respected in the join. -- ``join_axes`` : list of Index objects. Specific indexes to use for the other +* ``join_axes`` : list of Index objects. Specific indexes to use for the other n - 1 axes instead of performing inner/outer set logic. -- ``keys`` : sequence, default None. Construct hierarchical index using the +* ``keys`` : sequence, default None. Construct hierarchical index using the passed keys as the outermost level. If multiple levels passed, should contain tuples. -- ``levels`` : list of sequences, default None. Specific levels (unique values) +* ``levels`` : list of sequences, default None. Specific levels (unique values) to use for constructing a MultiIndex. Otherwise they will be inferred from the keys. -- ``names`` : list, default None. Names for the levels in the resulting +* ``names`` : list, default None. Names for the levels in the resulting hierarchical index. -- ``verify_integrity`` : boolean, default False. Check whether the new +* ``verify_integrity`` : boolean, default False. Check whether the new concatenated axis contains duplicates. This can be very expensive relative to the actual data concatenation. -- ``copy`` : boolean, default True. If False, do not copy data unnecessarily. +* ``copy`` : boolean, default True. If False, do not copy data unnecessarily. Without a little bit of context many of these arguments don't make much sense. Let's revisit the above example. Suppose we wanted to associate specific keys @@ -156,10 +156,10 @@ When gluing together multiple DataFrames, you have a choice of how to handle the other axes (other than the one being concatenated). This can be done in the following three ways: -- Take the union of them all, ``join='outer'``. This is the default +* Take the union of them all, ``join='outer'``. This is the default option as it results in zero information loss. -- Take the intersection, ``join='inner'``. -- Use a specific index, as passed to the ``join_axes`` argument. +* Take the intersection, ``join='inner'``. +* Use a specific index, as passed to the ``join_axes`` argument. Here is an example of each of these methods. First, the default ``join='outer'`` behavior: @@ -245,7 +245,7 @@ need to be: .. ipython:: python - result = df1.append(df4) + result = df1.append(df4, sort=False) .. ipython:: python :suppress: @@ -279,13 +279,13 @@ need to be: Ignoring indexes on the concatenation axis ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For ``DataFrame``s which don't have a meaningful index, you may wish to append -them and ignore the fact that they may have overlapping indexes. To do this, use -the ``ignore_index`` argument: +For ``DataFrame`` objects which don't have a meaningful index, you may wish +to append them and ignore the fact that they may have overlapping indexes. To +do this, use the ``ignore_index`` argument: .. ipython:: python - result = pd.concat([df1, df4], ignore_index=True) + result = pd.concat([df1, df4], ignore_index=True, sort=False) .. ipython:: python :suppress: @@ -299,7 +299,7 @@ This is also a valid argument to :meth:`DataFrame.append`: .. ipython:: python - result = df1.append(df4, ignore_index=True) + result = df1.append(df4, ignore_index=True, sort=False) .. ipython:: python :suppress: @@ -314,7 +314,7 @@ This is also a valid argument to :meth:`DataFrame.append`: Concatenating with mixed ndims ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can concatenate a mix of ``Series`` and ``DataFrame``s. The +You can concatenate a mix of ``Series`` and ``DataFrame`` objects. The ``Series`` will be transformed to ``DataFrame`` with the column name as the name of the ``Series``. @@ -494,7 +494,7 @@ You can also pass a list of dicts or Series: dicts = [{'A': 1, 'B': 2, 'C': 3, 'X': 4}, {'A': 5, 'B': 6, 'C': 7, 'Y': 8}] - result = df1.append(dicts, ignore_index=True) + result = df1.append(dicts, ignore_index=True, sort=False) .. ipython:: python :suppress: @@ -506,8 +506,8 @@ You can also pass a list of dicts or Series: .. _merging.join: -Database-style DataFrame joining/merging ----------------------------------------- +Database-style DataFrame or named Series joining/merging +-------------------------------------------------------- pandas has full-featured, **high performance** in-memory join operations idiomatically very similar to relational databases like SQL. These methods @@ -522,7 +522,7 @@ Users who are familiar with SQL but new to pandas might be interested in a :ref:`comparison with SQL`. pandas provides a single function, :func:`~pandas.merge`, as the entry point for -all standard database join operations between ``DataFrame`` objects: +all standard database join operations between ``DataFrame`` or named ``Series`` objects: :: @@ -531,52 +531,52 @@ all standard database join operations between ``DataFrame`` objects: suffixes=('_x', '_y'), copy=True, indicator=False, validate=None) -- ``left``: A DataFrame object. -- ``right``: Another DataFrame object. -- ``on``: Column or index level names to join on. Must be found in both the left - and right DataFrame objects. If not passed and ``left_index`` and +* ``left``: A DataFrame or named Series object. +* ``right``: Another DataFrame or named Series object. +* ``on``: Column or index level names to join on. Must be found in both the left + and right DataFrame and/or Series objects. If not passed and ``left_index`` and ``right_index`` are ``False``, the intersection of the columns in the - DataFrames will be inferred to be the join keys. -- ``left_on``: Columns or index levels from the left DataFrame to use as + DataFrames and/or Series will be inferred to be the join keys. +* ``left_on``: Columns or index levels from the left DataFrame or Series to use as keys. Can either be column names, index level names, or arrays with length - equal to the length of the DataFrame. -- ``right_on``: Columns or index levels from the right DataFrame to use as + equal to the length of the DataFrame or Series. +* ``right_on``: Columns or index levels from the right DataFrame or Series to use as keys. Can either be column names, index level names, or arrays with length - equal to the length of the DataFrame. -- ``left_index``: If ``True``, use the index (row labels) from the left - DataFrame as its join key(s). In the case of a DataFrame with a MultiIndex + equal to the length of the DataFrame or Series. +* ``left_index``: If ``True``, use the index (row labels) from the left + DataFrame or Series as its join key(s). In the case of a DataFrame or Series with a MultiIndex (hierarchical), the number of levels must match the number of join keys - from the right DataFrame. -- ``right_index``: Same usage as ``left_index`` for the right DataFrame -- ``how``: One of ``'left'``, ``'right'``, ``'outer'``, ``'inner'``. Defaults + from the right DataFrame or Series. +* ``right_index``: Same usage as ``left_index`` for the right DataFrame or Series +* ``how``: One of ``'left'``, ``'right'``, ``'outer'``, ``'inner'``. Defaults to ``inner``. See below for more detailed description of each method. -- ``sort``: Sort the result DataFrame by the join keys in lexicographical +* ``sort``: Sort the result DataFrame by the join keys in lexicographical order. Defaults to ``True``, setting to ``False`` will improve performance substantially in many cases. -- ``suffixes``: A tuple of string suffixes to apply to overlapping +* ``suffixes``: A tuple of string suffixes to apply to overlapping columns. Defaults to ``('_x', '_y')``. -- ``copy``: Always copy data (default ``True``) from the passed DataFrame +* ``copy``: Always copy data (default ``True``) from the passed DataFrame or named Series objects, even when reindexing is not necessary. Cannot be avoided in many cases but may improve performance / memory usage. The cases where copying can be avoided are somewhat pathological but this option is provided nonetheless. -- ``indicator``: Add a column to the output DataFrame called ``_merge`` +* ``indicator``: Add a column to the output DataFrame called ``_merge`` with information on the source of each row. ``_merge`` is Categorical-type and takes on a value of ``left_only`` for observations whose merge key - only appears in ``'left'`` DataFrame, ``right_only`` for observations whose - merge key only appears in ``'right'`` DataFrame, and ``both`` if the + only appears in ``'left'`` DataFrame or Series, ``right_only`` for observations whose + merge key only appears in ``'right'`` DataFrame or Series, and ``both`` if the observation's merge key is found in both. -- ``validate`` : string, default None. +* ``validate`` : string, default None. If specified, checks if merge is of specified type. - * "one_to_one" or "1:1": checks if merge keys are unique in both - left and right datasets. - * "one_to_many" or "1:m": checks if merge keys are unique in left - dataset. - * "many_to_one" or "m:1": checks if merge keys are unique in right - dataset. - * "many_to_many" or "m:m": allowed, but does not result in checks. + * "one_to_one" or "1:1": checks if merge keys are unique in both + left and right datasets. + * "one_to_many" or "1:m": checks if merge keys are unique in left + dataset. + * "many_to_one" or "m:1": checks if merge keys are unique in right + dataset. + * "many_to_many" or "m:m": allowed, but does not result in checks. .. versionadded:: 0.21.0 @@ -584,10 +584,10 @@ all standard database join operations between ``DataFrame`` objects: Support for specifying index levels as the ``on``, ``left_on``, and ``right_on`` parameters was added in version 0.23.0. + Support for merging named ``Series`` objects was added in version 0.24.0. -The return type will be the same as ``left``. If ``left`` is a ``DataFrame`` -and ``right`` is a subclass of DataFrame, the return type will still be -``DataFrame``. +The return type will be the same as ``left``. If ``left`` is a ``DataFrame`` or named ``Series`` +and ``right`` is a subclass of ``DataFrame``, the return type will still be ``DataFrame``. ``merge`` is a function in the pandas namespace, and it is also available as a ``DataFrame`` instance method :meth:`~DataFrame.merge`, with the calling @@ -605,11 +605,11 @@ terminology used to describe join operations between two SQL-table like structures (``DataFrame`` objects). There are several cases to consider which are very important to understand: -- **one-to-one** joins: for example when joining two ``DataFrame`` objects on +* **one-to-one** joins: for example when joining two ``DataFrame`` objects on their indexes (which must contain unique values). -- **many-to-one** joins: for example when joining an index (unique) to one or +* **many-to-one** joins: for example when joining an index (unique) to one or more columns in a different ``DataFrame``. -- **many-to-many** joins: joining columns on columns. +* **many-to-many** joins: joining columns on columns. .. note:: @@ -1085,12 +1085,12 @@ As you can see, this drops any rows where there was no match. .. _merging.join_on_mi: -Joining a single Index to a Multi-index -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Joining a single Index to a MultiIndex +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can join a singly-indexed ``DataFrame`` with a level of a multi-indexed ``DataFrame``. +You can join a singly-indexed ``DataFrame`` with a level of a MultiIndexed ``DataFrame``. The level will match on the name of the index of the singly-indexed frame against -a level name of the multi-indexed frame. +a level name of the MultiIndexed frame. .. ipython:: python @@ -1130,8 +1130,8 @@ This is equivalent but less verbose and more memory efficient / faster than this labels=['left', 'right'], vertical=False); plt.close('all'); -Joining with two multi-indexes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Joining with two MultiIndexes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is not implemented via ``join`` at-the-moment, however it can be done using the following code. @@ -1310,7 +1310,7 @@ For this, use the :meth:`~DataFrame.combine_first` method: Note that this method only takes values from the right ``DataFrame`` if they are missing in the left ``DataFrame``. A related method, :meth:`~DataFrame.update`, -alters non-NA values inplace: +alters non-NA values in place: .. ipython:: python :suppress: diff --git a/doc/source/missing_data.rst b/doc/source/missing_data.rst index 3950e4c80749b..e4b5578af15f0 100644 --- a/doc/source/missing_data.rst +++ b/doc/source/missing_data.rst @@ -105,7 +105,7 @@ Datetimes For datetime64[ns] types, ``NaT`` represents missing values. This is a pseudo-native sentinel value that can be represented by NumPy in a singular dtype (datetime64[ns]). -pandas objects provide intercompatibility between ``NaT`` and ``NaN``. +pandas objects provide compatibility between ``NaT`` and ``NaN``. .. ipython:: python @@ -349,7 +349,7 @@ Interpolation The ``limit_area`` keyword argument was added. Both Series and DataFrame objects have :meth:`~DataFrame.interpolate` -that, by default, performs linear interpolation at missing datapoints. +that, by default, performs linear interpolation at missing data points. .. ipython:: python :suppress: diff --git a/doc/source/names_wordlist.txt b/doc/source/names_wordlist.txt new file mode 100644 index 0000000000000..032883b7febf6 --- /dev/null +++ b/doc/source/names_wordlist.txt @@ -0,0 +1,1652 @@ +Critchley +Villanova +del +Hohmann +Rychyk +Buchkovsky +Lenail +Schade +datetimeindex +Aly +Sivji +Költringer +Bui +András +Novoszáth +Anh +Anil +Pallekonda +Pitrou +Linde +Quinonez +Varshokar +Artem +Bogachev +Avi +Azeez +Oluwafemi +Auffarth +Thiel +Bhavesh +Poddar +Haffner +Naul +Guinta +Moreira +García +Márquez +Cheuk +Chitrank +Dixit +Catalfo +Mazzullo +Chwala +Cihan +Ceyhan +Brunner +Riemenschneider +Dixey +Garrido +Sakuma +Hirschfeld +Adrián +Cañones +Castellano +Arcos +Hoese +Stansby +Kamau +Niederhut +Dror +Atariah +Chea +Kisslinger +Retkowski +Sar +Maeztu +Gianpaolo +Macario +Giftlin +Rajaiah +Olimpio +Gjelt +Inggs +Grzegorz +Konefał +Guilherme +Beltramini +Pitkeathly +Mashkoor +Ferchland +Haochen +Hissashi +Sharaf +Ignasi +Fosch +Alves +Shelvinskyi +Imanflow +Ingolf +Saeta +Pérez +Koevska +Jakub +Nowacki +Werkmann +Zoutkamp +Bandlow +Jaume +Bonet +Alammar +Reback +Jing +Qiang +Goh +Miralles +Nothman +Joeun +Metz +Mease +Schulze +Jongwony +Jordi +Contestí +Joris +Bossche +José +Fonseca +Jovixe +Jörg +Döpfert +Ittoku +Surta +Kuhl +Krzysztof +Chomski +Ksenia +Ksenia +Bobrova +Kunal +Gosar +Kerstein +Laksh +Arora +Geffert +Licht +Takeuchi +Liudmila +Villalba +Manan +Singh +Manraj +Singh +Hemken +Bibiloni +Corchero +Woodbridge +Journois +Gallo +Heikkilä +Braymer +Maybeno +Rocklin +Roeschke +Bussonnier +Mikhaylov +Veksler +Roos +Maximiliano +Greco +Penkov +Röttger +Selik +Waskom +Mie +Kutzma +Mitar +Negus +Münst +Mortada +Mehyar +Braithwaite +Chmura +Karagiannakis +Nipun +Sadvilkar +Martensen +Noémi +Éltető +Bilodeau +Ondrej +Kokes +Onno +Ganssle +Mannino +Reidy +Oliveira +Hoffmann +Ngo +Battiston +Pranav +Suri +Priyanka +Ojha +Pulkit +Maloo +Magliocchetti +Ridhwan +Luthra +Kiplang'at +Rohan +Pandit +Rok +Mihevc +Rouz +Azari +Ryszard +Kaleta +Samir +Musali +Sinayoko +Sangwoong +Yoon +Sharad +Vijalapuram +Shubham +Chaudhary +Sietse +Brouwer +Delprete +Cianciulli +Childs +Stijn +Hoey +Talitha +Pumar +Tarbo +Fukazawa +Petrou +Caswell +Hoffmann +Swast +Augspurger +Tulio +Casagrande +Tushar +Tushar +Mittal +Upkar +Lidder +Vinícius +Figueiredo +Vipin +WBare +Wenhuan +Ayd +Xbar +Yaroslav +Halchenko +Yee +Mey +Yeongseon +Choe +Yian +Yimeng +Zhang +Zihao +Zhao +adatasetaday +akielbowicz +akosel +alinde +amuta +bolkedebruin +cbertinato +cgohlke +charlie +chris +csfarkas +dajcs +deflatSOCO +derestle +htwg +discort +dmanikowski +donK +elrubio +fivemok +fjdiod +fjetter +froessler +gabrielclow +gfyoung +ghasemnaddaf +vetinari +himanshu +awasthi +ignamv +jayfoad +jazzmuesli +jbrockmendel +jjames +joaoavf +joders +jschendel +juan +huguet +luzpaz +mdeboc +miguelmorin +miker +miquelcamprodon +orereta +ottiP +peterpanmj +rafarui +raph +readyready +rmihael +samghelms +scriptomation +sfoo +stefansimik +stonebig +tmnhat +tomneep +tv +verakai +xpvpc +zhanghui +API +Mazzullo +Riemenschneider +Hirschfeld +Stansby +Dror +Atariah +Kisslinger +Ingolf +Werkmann +Reback +Joris +Bossche +Jörg +Döpfert +Kuhl +Krzysztof +Chomski +Licht +Takeuchi +Manraj +Singh +Braymer +Waskom +Mie +Hoffmann +Sietse +Brouwer +Swast +Augspurger +Ayd +Yee +Mey +bolkedebruin +cgohlke +derestle +htwg +fjdiod +gabrielclow +gfyoung +ghasemnaddaf +jbrockmendel +jschendel +miker +pypy +Gleave +Liaw +Velasco +Yee +Marchenko +Amol +Winkler +亮 +André +Jonasson +Sweger +Berkay +Haffner +Tu +Chankey +Pathak +Billington +Filo +Gorgolewski +Mazzullo +Prinoth +Stade +Schuldt +Moehl +Himmelstein +Willmer +Niederhut +Wieser +Fredriksen +Kint +Giftlin +Giftlin +Rajaiah +Guilherme +Beltramini +Guillem +Borrell +Hanmin +Qin +Makait +Hussain +Tamboli +Miholic +Novotný +Helie +Schiratti +Deschenes +Knupp +Reback +Tratner +Nothman +Crall +Mease +Helmus +Joris +Bossche +Bochi +Kuhlmann +Brabandere +Keeton +Keiron +Pizzey +Kernc +Licht +Takeuchi +Kushner +Jelloul +Makarov +Malgorzata +Turzanska +Sy +Roeschke +Picus +Mehmet +Akmanalp +Gasvoda +Penkov +Eubank +Shteynbuk +Tillmann +Pankaj +Pandey +Luo +O'Melveny +Reidy +Quackenbush +Yanovich +Haessig +Battiston +Pradyumna +Reddy +Chinthala +Prasanjit +Prakash +Sangwoong +Yoon +Sudeep +Telt +Caswell +Swast +Augspurger +Tuan +Utkarsh +Upadhyay +Vivek +Aiyong +WBare +Yi +Liu +Yosuke +Nakabayashi +aaron +abarber +gh +aernlund +agustín +méndez +andymaheshw +aviolov +bpraggastis +cbertinato +cclauss +chernrick +chris +dkamm +dwkenefick +faic +fding +gfyoung +guygoldberg +hhuuggoo +huashuai +ian +iulia +jaredsnyder +jbrockmendel +jdeschenes +jebob +jschendel +keitakurita +kernc +kiwirob +kjford +linebp +lloydkirk +louispotok +majiang +manikbhandari +matthiashuschle +mattip +maxwasserman +mjlove +nmartensen +parchd +philipphanemann +rdk +reidy +ri +ruiann +rvernica +weigand +scotthavard +skwbc +tobycheese +tsdlovell +ysau +zzgao +cov +abaldenko +adrian +stepien +Saxena +Akash +Tandon +Aleksey +Bilogur +alexandercbooth +Amol +Kahat +Winkler +Kittredge +Anthonios +Partheniou +Arco +Ashish +Singal +atbd +bastewart +Baurzhan +Muftakhidinov +Kandel +bmagnusson +carlosdanielcsantos +Souza +chaimdemulder +chris +Aycock +Gohlke +Paulik +Warth +Brunner +Himmelstein +Willmer +Krych +dickreuter +Dimitris +Spathis +discort +Dmitry +Suria +Wijaya +Stanczak +dr +leo +dubourg +dwkenefick +Andrade +Ennemoser +Francesc +Alted +Fumito +Hamamura +funnycrab +gfyoung +Ferroni +goldenbull +Jeffries +Guilherme +Beltramini +Guilherme +Samora +Hao +Harshit +Patni +Ilya +Schurov +Iván +Vallés +Pérez +Leng +Jaehoon +Hwang +Goppert +Santucci +Reback +Crist +Jevnik +Nothman +Zwinck +jojomdt +Whitmore +Mease +Mease +Joost +Kranendonk +Joris +Bossche +Bradt +Santander +Julien +Marrec +Solinsky +Kacawi +Kamal +Kamalaldin +Shedden +Kernc +Keshav +Ramaswamy +Ren +linebp +Pedersen +Cestaro +Scarabello +Lukasz +paramstyle +Lababidi +Unserialized +manu +manuels +Roeschke +mattip +Picus +Roeschke +maxalbert +Roos +mcocdawc +Lamparski +Michiel +Mikolaj +Chwalisz +Miroslav +Šedivý +Mykola +Golubyev +Rud +Halen +Chmura +nuffe +Pankaj +Pandey +paul +mannino +Pawel +Kordek +pbreach +Csizsek +Petio +Petrov +Ruffwind +Battiston +Chromiec +Prasanjit +Prakash +Forgione +Rouz +Azari +Sahil +Dua +sakkemo +Sami +Salonen +Sarma +Tangirala +scls +Gsänger +Sébastien +Menten +Heide +Shyam +Saladi +sinhrks +Sinhrks +Rauch +stijnvanhoey +Adiseshan +themrmax +Thiago +Serafim +Thoralf +Thrasibule +Gustafsson +Augspurger +tomrod +Shen +tzinckgraf +Uwe +wandersoncferreira +watercrossing +wcwagner +Wiktor +Tomczak +xgdgsc +Yaroslav +Halchenko +Yimeng +Zhang +yui +knk +Saxena +Kandel +Aycock +Himmelstein +Willmer +gfyoung +hesham +shabana +Reback +Jevnik +Joris +Bossche +Santander +Shedden +Keshav +Ramaswamy +Scarabello +Picus +Roeschke +Roos +Mykola +Golubyev +Halen +Pawel +Kordek +Battiston +sinhrks +Adiseshan +Augspurger +wandersoncferreira +Yaroslav +Halchenko +Chainz +Anthonios +Partheniou +Arash +Rouhani +Kandel +chris +Warth +Krych +dubourg +gfyoung +Iván +Vallés +Pérez +Reback +Jevnik +Mease +Joris +Bossche +Keshav +Ramaswamy +Ren +mattrijk +paul +mannino +Chromiec +Sinhrks +Thiago +Serafim +adneu +agraboso +Alekseyev +Vig +Riddell +Amol +Amol +Agrawal +Anthonios +Partheniou +babakkeyvani +Kandel +Baxley +Camilo +Cota +chris +Grinolds +Hudon +Aycock +Warth +cmazzullo +cr +Siladji +Drewrey +Lupton +dsm +Blancas +Marsden +Marczinowski +O'Donovan +Gábor +Lipták +Geraint +gfyoung +Ferroni +Haleemur +harshul +Hassan +Shamim +iamsimha +Iulius +Nazarov +jackieleng +Reback +Crist +Jevnik +Liekezer +Zwinck +Erenrich +Joris +Bossche +Howes +Brandys +Kamil +Sindi +Ka +Wo +Shedden +Kernc +Brucher +Roos +Scherer +Mortada +Mehyar +mpuels +Haseeb +Tariq +Bonnotte +Virtanen +Mestemaker +Pawel +Kordek +Battiston +pijucha +Jucha +priyankjain +Nimmi +Gieseke +Keyes +Sahil +Dua +Sanjiv +Lobo +Sašo +Stanovnik +Heide +sinhrks +Sinhrks +Kappel +Choi +Sudarshan +Konge +Caswell +Augspurger +Uwe +Hoffmann +wcwagner +Xiang +Zhang +Yadunandan +Yaroslav +Halchenko +YG +Riku +Yuichiro +Kaneko +yui +knk +zhangjinjie +znmean +颜发才 +Yan +Facai +Fiore +Gartland +Bastiaan +Benoît +Vinot +Fustin +Freitas +Ter +Livschitz +Gábor +Lipták +Hassan +Kibirige +Iblis +Saeta +Pérez +Wolosonovich +Reback +Jevnik +Joris +Bossche +Storck +Ka +Wo +Shedden +Kieran +O'Mahony +Lababidi +Maoyuan +Liu +Wittmann +MaxU +Roos +Droettboom +Eubank +Bonnotte +Virtanen +Battiston +Prabhjot +Singh +Augspurger +Aiyong +Winand +Xbar +Yan +Facai +adneu +ajenkins +cargometrics +behzad +nouri +chinskiy +gfyoung +jeps +jonaslb +kotrfa +nileracecrew +onesandzeroes +sinhrks +tsdlovell +Alekseyev +Rosenfeld +Anthonios +Partheniou +Sipos +Carroux +Aycock +Scanlin +Da +Dorozhko +O'Donovan +Cleary +Gianluca +Jeffries +Horel +Schwabacher +Deschenes +Reback +Jevnik +Fremlin +Hoersch +Joris +Bossche +Joris +Vankerschaver +Ka +Wo +Keming +Zhang +Shedden +Farrugia +Lurie +Roos +Mayank +Asthana +Mortada +Mehyar +Moussa +Taifi +Navreet +Bonnotte +Reiners +Gura +Battiston +Carnevale +Rinoc +Rishipuri +Sangmin +Lasley +Sereger +Seabold +Thierry +Moisan +Caswell +Augspurger +Hauck +Varun +Yoong +Kang +Lim +Yoshiki +Vázquez +Baeza +Joong +Younggun +Yuval +Langer +argunov +behzad +nouri +boombard +brian +pantano +chromy +daniel +dgram +gfyoung +hcontrast +jfoo +kaustuv +deolal +llllllllll +ranarag +rockg +scls +seales +sinhrks +srib +surveymedia +tworec +Drozd +Anthonios +Partheniou +Berendt +Piersall +Hamed +Saljooghinejad +Iblis +Deschenes +Reback +Callin +Joris +Bossche +Ka +Wo +Loïc +Séguin +Luo +Yicheng +Magnus +Jöud +Leonhardt +Roos +Bonnotte +Pastafarianist +Chong +Schaf +Philipp +deCarvalho +Khomenko +Rémy +Léone +Thierry +Moisan +Augspurger +Varun +Hoffmann +Winterflower +Younggun +ajcr +azuranski +behzad +nouri +cel +emilydolson +hironow +lexual +llllllllll +rockg +silentquasar +sinhrks +taeold +unparseable +Rothberg +Bedini +Rosenfeld +Anthonios +Partheniou +Artemy +Kolchinsky +Willers +Gohlke +Clearfield +Ringwalt +Cottrell +Gagne +Schettino +Panfilov +Araujo +Gianluca +Poulin +Nisar +Henriksen +Hoegen +Jaidev +Deshpande +Swails +Reback +Buyl +Joris +Bossche +Joris +Vankerschaver +Julien +Danjou +Ka +Wo +Kehoe +Jordahl +Shedden +Buitinck +Gambogi +Savoie +Roos +D'Agostino +Mortada +Mehyar +Eubank +Nipun +Batra +Ondřej +Čertík +Pratap +Vardhan +Rafal +Skolasinski +Rinoc +Gieseke +Safia +Abdalla +Saumitra +Shahapure +Pölsterl +Rubbert +Sinhrks +Siu +Kwan +Seabold +Carrucciu +Hoyer +Pascoe +Santegoeds +Grainger +Tjerk +Santegoeds +Augspurger +Winterflower +Yaroslav +Halchenko +agijsberts +ajcr +behzad +nouri +cel +cyrusmaher +davidovitch +ganego +jreback +juricast +larvian +maximilianr +msund +rekcahpassyla +robertzk +scls +seth +sinhrks +springcoil +terrytangyuan +tzinckgraf +Rosenfeld +Artemy +Kolchinsky +Willers +Christer +der +Meeren +Hudon +Lasiman +Brundu +Gaëtan +Menten +Hiebert +Reback +Joris +Bossche +Ka +Wo +Mortada +Mehyar +Grainger +Ajamian +Augspurger +Yoshiki +Vázquez +Baeza +Younggun +austinc +behzad +nouri +jreback +lexual +rekcahpassyla +scls +sinhrks +Artemy +Kolchinsky +Gilmer +Grinolds +Birken +Hirschfeld +Dunné +Hatem +Nassrat +Sperr +Herter +Blackburne +Reback +Crist +Abernot +Joris +Bossche +Shedden +Razoumov +Riel +Mortada +Mehyar +Eubank +Grisel +Battiston +Hyunjin +Zhang +Hoyer +Tiago +Antao +Ajamian +Augspurger +Tomaz +Berisa +Shirgur +Filimonov +Hogman +Yasin +Younggun +behzad +nouri +dsm +floydsoft +gfr +jnmclarty +jreback +ksanghai +lucas +mschmohl +ptype +rockg +scls +sinhrks +Toth +Amici +Artemy +Kolchinsky +Ashwini +Chaudhary +Letson +Chau +Hoang +Christer +der +Meeren +Cottrell +Ehsan +Azarnasab +Torcasso +Sexauer +Reback +Joris +Bossche +Joschka +zur +Jacobsmühlen +Bochi +Junya +Hayashi +Shedden +Kieran +O'Mahony +Kodi +Arfer +Airas +Mortada +Mehyar +Lasley +Lasley +Pascual +Seabold +Hoyer +Grainger +Augspurger +Filimonov +Vyomkesh +Tripathi +Holmgren +Yulong +behzad +nouri +bertrandhaut +bjonen +cel +clham +hsperr +ischwabacher +jnmclarty +josham +jreback +omtinez +roch +sinhrks +unutbu +Angelos +Evripiotis +Artemy +Kolchinsky +Pointet +Jacobowski +Charalampos +Papaloizou +Warth +Zanini +Francesc +Kleynhans +Reback +Tratner +Joris +Bossche +Suggit +Lasley +Hoyer +Sylvain +Corlay +Grainger +Tiago +Antao +Hauck +Chaves +Salgado +Bhandoh +Aiyong +Holmgren +behzad +nouri +broessli +charalampos +papaloizou +immerrr +jnmclarty +jreback +mgilbert +onesandzeroes +peadarcoyle +rockg +seth +sinhrks +unutbu +wavedatalab +Åsmund +Hjulstad +Rosenfeld +Sipos +Artemy +Kolchinsky +Letson +Horel +Reback +Joris +Bossche +Sanghee +Hoyer +Aiyong +behzad +nouri +immerrr +jnmclarty +jreback +pallav +fdsi +unutbu +Greenhall +Artemy +Kolchinsky +behzad +nouri +Sauer +benjamin +Thyreau +bjonen +Stoafer +dlovell +dsm +Herrero +Hsiaoming +Huan +hunterowens +Hyungtae +immerrr +Slavitt +ischwabacher +Schaer +Tratner +Farnham +jmorris +jnmclarty +Bradish +Joerg +Rittinger +Joris +Bossche +jreback +klonuo +lexual +mcjcode +Schatzow +Mortada +Mehyar +mtrbean +Typanski +onesandzeroes +Masurel +Battiston +rockg +Petchler +seth +Shahul +Hameed +Shashank +Agarwal +sinhrks +someben +stahlous +stas +sl +Hoyer +thatneat +alcorn +Augspurger +unutbu +Yevgeniy +Grechka +Yoshiki +VÃ +zquez +Baeza +zachcp +Rosenfeld +Quistorff +Wignall +bwignall +clham +Waeber +Bew +dsm +helger +immerrr +Schaer +jaimefrio +Reaver +Joris +Bossche +jreback +Julien +Danjou +lexual +Wittmann +Mortada +Mehyar +onesandzeroes +rockg +sanguineturtle +Schaer +seth +sinhrks +Hoyer +Kluyver +yelite +hexbin +Acanthostega +agijsberts +akittredge +Gaudio +Rothberg +Rosenfeld +ankostis +anomrake +Mazières +anton +bashtage +Sauer +benjamin +Buran +bwignall +cgohlke +chebee +clham +Birken +danielballan +Waeber +Drapala +Gouthaman +Balaraman +Poulin +hshimizu +hugo +immerrr +ischwabacher +Schaer +jaimefrio +Sexauer +Reback +Tratner +Reaver +Joris +Bossche +jreback +jsexauer +Júlio +kdiether +Jordahl +Wittmann +Grender +Gruen +michaelws +mikebailey +Nipun +Batra +ojdo +onesandzeroes +phaebz +Battiston +Carnevale +ribonoous +Gibboni +rockg +sinhrks +Seabold +Hoyer +Cera +Augspurger +unutbu +westurner +Yaroslav +Halchenko +lexual +danbirken +travis +Billington +Cobzarenco +Gamboa +Cavazos +Gaudecker +Gerigk +Yaroslav +Halchenko +sharey +Vytautas +Jancauskas +Hammerbacher +Hilboll +Luc +Kesters +JanSchulz +Negusse +Wouter +Overmeire +Reeson +Aman +Thakral +Uga +Vandenbussche +Pinxing +astype +Buglet +Beltrame +Hilboll +Jev +Kuznetsov +Wouter +Overmeire +Reyfman +Joon +Ro +Uga +Vandenbussche +setupegg +Hammerbacher +Jev +Kuznetsov +Wouter +Overmeire +Aman +Thakral +Uga +Vandenbussche +carljv +rsamson +newaxis +Fortunov +Aman +Thakral +Beltrame +Wouter +Overmeire +rsamson +Laserson +Pentreath +Joon +Ro +Uga +Fortunov +Berka +Vandenbussche +krogh +akima +BPoly +isna +kurt diff --git a/doc/source/options.rst b/doc/source/options.rst index 48247eb48baaf..dc4d0da32008c 100644 --- a/doc/source/options.rst +++ b/doc/source/options.rst @@ -31,10 +31,10 @@ You can get/set options directly as attributes of the top-level ``options`` attr The API is composed of 5 relevant functions, available directly from the ``pandas`` namespace: -- :func:`~pandas.get_option` / :func:`~pandas.set_option` - get/set the value of a single option. -- :func:`~pandas.reset_option` - reset one or more options to their default value. -- :func:`~pandas.describe_option` - print the descriptions of one or more options. -- :func:`~pandas.option_context` - execute a codeblock with a set of options +* :func:`~pandas.get_option` / :func:`~pandas.set_option` - get/set the value of a single option. +* :func:`~pandas.reset_option` - reset one or more options to their default value. +* :func:`~pandas.describe_option` - print the descriptions of one or more options. +* :func:`~pandas.option_context` - execute a codeblock with a set of options that revert to prior settings after execution. **Note:** Developers can check out `pandas/core/config.py `_ for more information. @@ -137,7 +137,7 @@ Using startup scripts for the python/ipython environment to import pandas and se $IPYTHONDIR/profile_default/startup More information can be found in the `ipython documentation -`__. An example startup script for pandas is displayed below: +`__. An example startup script for pandas is displayed below: .. code-block:: python @@ -149,7 +149,7 @@ More information can be found in the `ipython documentation Frequently Used Options ----------------------- -The following is a walkthrough of the more frequently used display options. +The following is a walk-through of the more frequently used display options. ``display.max_rows`` and ``display.max_columns`` sets the maximum number of rows and columns displayed when a frame is pretty-printed. Truncated diff --git a/doc/source/overview.rst b/doc/source/overview.rst index f86b1c67e6843..b71f4bfa2f3be 100644 --- a/doc/source/overview.rst +++ b/doc/source/overview.rst @@ -12,19 +12,19 @@ programming language. :mod:`pandas` consists of the following elements: - * A set of labeled array data structures, the primary of which are - Series and DataFrame. - * Index objects enabling both simple axis indexing and multi-level / - hierarchical axis indexing. - * An integrated group by engine for aggregating and transforming data sets. - * Date range generation (date_range) and custom date offsets enabling the - implementation of customized frequencies. - * Input/Output tools: loading tabular data from flat files (CSV, delimited, - Excel 2003), and saving and loading pandas objects from the fast and - efficient PyTables/HDF5 format. - * Memory-efficient "sparse" versions of the standard data structures for storing - data that is mostly missing or mostly constant (some fixed value). - * Moving window statistics (rolling mean, rolling standard deviation, etc.). +* A set of labeled array data structures, the primary of which are + Series and DataFrame. +* Index objects enabling both simple axis indexing and multi-level / + hierarchical axis indexing. +* An integrated group by engine for aggregating and transforming data sets. +* Date range generation (date_range) and custom date offsets enabling the + implementation of customized frequencies. +* Input/Output tools: loading tabular data from flat files (CSV, delimited, + Excel 2003), and saving and loading pandas objects from the fast and + efficient PyTables/HDF5 format. +* Memory-efficient "sparse" versions of the standard data structures for storing + data that is mostly missing or mostly constant (some fixed value). +* Moving window statistics (rolling mean, rolling standard deviation, etc.). Data Structures --------------- @@ -82,7 +82,7 @@ Getting Support The first stop for pandas issues and ideas is the `Github Issue Tracker `__. If you have a general question, pandas community experts can answer through `Stack Overflow -`__. +`__. Community --------- @@ -92,7 +92,7 @@ the world who contribute their valuable time and energy to help make open source pandas possible. Thanks to `all of our contributors `__. If you're interested in contributing, please -visit `Contributing to pandas webpage `__. +visit `Contributing to pandas webpage `__. pandas is a `NumFOCUS `__ sponsored project. This will help ensure the success of development of pandas as a world-class open-source diff --git a/doc/source/release.rst b/doc/source/release.rst index 32db2ff5ebb24..af6fc23e12b78 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -20,7 +20,7 @@ Release Notes ************* This is the list of changes to pandas between each release. For full details, -see the commit logs at http://github.com/pandas-dev/pandas +see the commit logs at https://github.com/pandas-dev/pandas **What is it** @@ -33,14 +33,95 @@ analysis / manipulation tool available in any language. **Where to get it** -* Source code: http://github.com/pandas-dev/pandas +* Source code: https://github.com/pandas-dev/pandas * Binary installers on PyPI: https://pypi.org/project/pandas -* Documentation: http://pandas.pydata.org +* Documentation: https://pandas.pydata.org + +pandas 0.23.2 +------------- + +**Release date**: July 5, 2018 + +This is a minor bug-fix release in the 0.23.x series and includes some small regression fixes +and bug fixes. + +See the :ref:`full whatsnew ` for a list of all the changes. + +Thanks +~~~~~~ + +A total of 17 people contributed to this release. People with a "+" by their +names contributed a patch for the first time. + +* David Krych +* Jacopo Rota + +* Jeff Reback +* Jeremy Schendel +* Joris Van den Bossche +* Kalyan Gokhale +* Matthew Roeschke +* Michael Odintsov + +* Ming Li +* Pietro Battiston +* Tom Augspurger +* Uddeshya Singh +* Vu Le + +* alimcmaster1 + +* david-liu-brattle-1 + +* gfyoung +* jbrockmendel + +pandas 0.23.1 +------------- + +**Release date**: June 12, 2018 + +This is a minor release from 0.23.0 and includes a number of bug fixes and +performance improvements. + +See the :ref:`full whatsnew ` for a list of all the changes. + +Thanks +~~~~~~ + +A total of 30 people contributed to this release. People with a "+" by their +names contributed a patch for the first time. + +* Adam J. Stewart +* Adam Kim + +* Aly Sivji +* Chalmer Lowe + +* Damini Satya + +* Dr. Irv +* Gabe Fernando + +* Giftlin Rajaiah +* Jeff Reback +* Jeremy Schendel + +* Joris Van den Bossche +* Kalyan Gokhale + +* Kevin Sheppard +* Matthew Roeschke +* Max Kanter + +* Ming Li +* Pyry Kovanen + +* Stefano Cianciulli +* Tom Augspurger +* Uddeshya Singh + +* Wenhuan +* William Ayd +* chris-b1 +* gfyoung +* h-vetinari +* nprad + +* ssikdar1 + +* tmnhat2001 +* topper-123 +* zertrin + pandas 0.23.0 ------------- -**Release date**: May 15, 2017 +**Release date**: May 15, 2018 This is a major release from 0.22.0 and includes a number of API changes, new features, enhancements, and performance improvements along with a large number @@ -505,7 +586,7 @@ Highlights include: - Integration with `Apache Parquet `__, including a new top-level :func:`read_parquet` function and :meth:`DataFrame.to_parquet` method, see :ref:`here `. - New user-facing :class:`pandas.api.types.CategoricalDtype` for specifying categoricals independent of the data, see :ref:`here `. -- The behavior of ``sum`` and ``prod`` on all-NaN Series/DataFrames is now consistent and no longer depends on whether `bottleneck `__ is installed, and ``sum`` and ``prod`` on empty Series now return NaN instead of 0, see :ref:`here `. +- The behavior of ``sum`` and ``prod`` on all-NaN Series/DataFrames is now consistent and no longer depends on whether `bottleneck `__ is installed, and ``sum`` and ``prod`` on empty Series now return NaN instead of 0, see :ref:`here `. - Compatibility fixes for pypy, see :ref:`here `. - Additions to the ``drop``, ``reindex`` and ``rename`` API to make them more consistent, see :ref:`here `. - Addition of the new methods ``DataFrame.infer_objects`` (see :ref:`here `) and ``GroupBy.pipe`` (see :ref:`here `). @@ -1090,7 +1171,7 @@ Highlights include: - Sparse data structures gained enhanced support of ``int`` and ``bool`` dtypes, see :ref:`here ` - Comparison operations with ``Series`` no longer ignores the index, see :ref:`here ` for an overview of the API changes. - Introduction of a pandas development API for utility functions, see :ref:`here `. -- Deprecation of ``Panel4D`` and ``PanelND``. We recommend to represent these types of n-dimensional data with the `xarray package `__. +- Deprecation of ``Panel4D`` and ``PanelND``. We recommend to represent these types of n-dimensional data with the `xarray package `__. - Removal of the previously deprecated modules ``pandas.io.data``, ``pandas.io.wb``, ``pandas.tools.rplot``. See the :ref:`v0.19.0 Whatsnew ` overview for an extensive list @@ -1321,7 +1402,7 @@ Highlights include: - Removal of support for positional indexing with floats, which was deprecated since 0.14.0. This will now raise a ``TypeError``, see :ref:`here `. - The ``.to_xarray()`` function has been added for compatibility with the - `xarray package `__, see :ref:`here `. + `xarray package `__, see :ref:`here `. - The ``read_sas`` function has been enhanced to read ``sas7bdat`` files, see :ref:`here `. - Addition of the :ref:`.str.extractall() method `, and API changes to the :ref:`.str.extract() method ` @@ -1676,7 +1757,7 @@ along with several new features, enhancements, and performance improvements. Highlights include: - A new ``pipe`` method, see :ref:`here ` -- Documentation on how to use `numba `_ with *pandas*, see :ref:`here ` +- Documentation on how to use `numba `_ with *pandas*, see :ref:`here ` See the :ref:`v0.16.2 Whatsnew ` overview for an extensive list of all enhancements and bugs that have been fixed in 0.16.2. @@ -1808,9 +1889,9 @@ Highlights include: - Changes to the default for ordering in the ``Categorical`` constructor, see :ref:`here ` - The ``pandas.tools.rplot``, ``pandas.sandbox.qtpandas`` and ``pandas.rpy`` modules are deprecated. We refer users to external packages like - `seaborn `_, + `seaborn `_, `pandas-qt `_ and - `rpy2 `_ for similar or equivalent + `rpy2 `_ for similar or equivalent functionality, see :ref:`here ` See the :ref:`v0.16.0 Whatsnew ` overview or the issue tracker on GitHub for an extensive list @@ -2174,7 +2255,7 @@ Highlights include: - SQL interfaces updated to use ``sqlalchemy``, see :ref:`here`. - Display interface changes, see :ref:`here` - MultiIndexing using Slicers, see :ref:`here`. -- Ability to join a singly-indexed DataFrame with a multi-indexed DataFrame, see :ref:`here ` +- Ability to join a singly-indexed DataFrame with a MultiIndexed DataFrame, see :ref:`here ` - More consistency in groupby results and more flexible groupby specifications, see :ref:`here` - Holiday calendars are now supported in ``CustomBusinessDay``, see :ref:`here ` - Several improvements in plotting functions, including: hexbin, area and pie plots, see :ref:`here`. @@ -2384,8 +2465,8 @@ Bug Fixes - Bug in merging ``timedelta`` dtypes (:issue:`5695`) - Bug in plotting.scatter_matrix function. Wrong alignment among diagonal and off-diagonal plots, see (:issue:`5497`). -- Regression in Series with a multi-index via ix (:issue:`6018`) -- Bug in Series.xs with a multi-index (:issue:`6018`) +- Regression in Series with a MultiIndex via ix (:issue:`6018`) +- Bug in Series.xs with a MultiIndex (:issue:`6018`) - Bug in Series construction of mixed type with datelike and an integer (which should result in object type and not automatic conversion) (:issue:`6028`) - Possible segfault when chained indexing with an object array under NumPy 1.7.1 (:issue:`6026`, :issue:`6056`) @@ -2409,10 +2490,10 @@ Bug Fixes - Fixed a bug in ``query``/``eval`` during lexicographic string comparisons (:issue:`6155`). - Fixed a bug in ``query`` where the index of a single-element ``Series`` was being thrown away (:issue:`6148`). -- Bug in ``HDFStore`` on appending a dataframe with multi-indexed columns to +- Bug in ``HDFStore`` on appending a dataframe with MultiIndexed columns to an existing table (:issue:`6167`) - Consistency with dtypes in setting an empty DataFrame (:issue:`6171`) -- Bug in selecting on a multi-index ``HDFStore`` even in the presence of under +- Bug in selecting on a MultiIndex ``HDFStore`` even in the presence of under specified column spec (:issue:`6169`) - Bug in ``nanops.var`` with ``ddof=1`` and 1 elements would sometimes return ``inf`` rather than ``nan`` on some platforms (:issue:`6136`) @@ -2429,7 +2510,7 @@ New Features - ``plot(kind='kde')`` now accepts the optional parameters ``bw_method`` and ``ind``, passed to scipy.stats.gaussian_kde() (for scipy >= 0.11.0) to set - the bandwidth, and to gkde.evaluate() to specify the indicies at which it + the bandwidth, and to gkde.evaluate() to specify the indices at which it is evaluated, respectively. See scipy docs. (:issue:`4298`) - Added ``isin`` method to DataFrame (:issue:`4211`) - ``df.to_clipboard()`` learned a new ``excel`` keyword that let's you @@ -2540,7 +2621,7 @@ Improvements to existing features - ``read_json`` now raises a (more informative) ``ValueError`` when the dict contains a bad key and ``orient='split'`` (:issue:`4730`, :issue:`4838`) - ``read_stata`` now accepts Stata 13 format (:issue:`4291`) -- ``ExcelWriter`` and ``ExcelFile`` can be used as contextmanagers. +- ``ExcelWriter`` and ``ExcelFile`` can be used as context managers. (:issue:`3441`, :issue:`4933`) - ``pandas`` is now tested with two different versions of ``statsmodels`` (0.4.3 and 0.5.0) (:issue:`4981`). @@ -2553,7 +2634,7 @@ Improvements to existing features that cannot be concatenated (:issue:`4608`). - Add ``halflife`` option to exponentially weighted moving functions (PR :issue:`4998`) -- ``to_dict`` now takes ``records`` as a possible outtype. Returns an array +- ``to_dict`` now takes ``records`` as a possible out type. Returns an array of column-keyed dictionaries. (:issue:`4936`) - ``tz_localize`` can infer a fall daylight savings transition based on the structure of unlocalized data (:issue:`4230`) @@ -2594,7 +2675,7 @@ Improvements to existing features option it is no longer possible to round trip Excel files with merged MultiIndex and Hierarchical Rows. Set the ``merge_cells`` to ``False`` to restore the previous behaviour. (:issue:`5254`) -- The FRED DataReader now accepts multiple series (:issue`3413`) +- The FRED DataReader now accepts multiple series (:issue:`3413`) - StataWriter adjusts variable names to Stata's limitations (:issue:`5709`) API Changes @@ -2659,18 +2740,18 @@ API Changes - the ``format`` keyword now replaces the ``table`` keyword; allowed values are ``fixed(f)|table(t)`` the ``Storer`` format has been renamed to ``Fixed`` - - a column multi-index will be recreated properly (:issue:`4710`); raise on - trying to use a multi-index with data_columns on the same axis + - a column MultiIndex will be recreated properly (:issue:`4710`); raise on + trying to use a MultiIndex with data_columns on the same axis - ``select_as_coordinates`` will now return an ``Int64Index`` of the resultant selection set - support ``timedelta64[ns]`` as a serialization type (:issue:`3577`) - - store `datetime.date` objects as ordinals rather then timetuples to avoid + - store `datetime.date` objects as ordinals rather then time-tuples to avoid timezone issues (:issue:`2852`), thanks @tavistmorph and @numpand - ``numexpr`` 2.2.2 fixes incompatibility in PyTables 2.4 (:issue:`4908`) - ``flush`` now accepts an ``fsync`` parameter, which defaults to ``False`` (:issue:`5364`) - ``unicode`` indices not supported on ``table`` formats (:issue:`5386`) - - pass thru store creation arguments; can be used to support in-memory stores + - pass through store creation arguments; can be used to support in-memory stores - ``JSON`` - added ``date_unit`` parameter to specify resolution of timestamps. @@ -2736,7 +2817,7 @@ API Changes created when passing floating values in index creation. This enables a pure label-based slicing paradigm that makes ``[],ix,loc`` for scalar indexing and slicing work exactly the same. Indexing on other index types - are preserved (and positional fallback for ``[],ix``), with the exception, + are preserved (and positional fall back for ``[],ix``), with the exception, that floating point slicing on indexes on non ``Float64Index`` will raise a ``TypeError``, e.g. ``Series(range(5))[3.5:4.5]`` (:issue:`263`,:issue:`5375`) - Make Categorical repr nicer (:issue:`4368`) @@ -2765,7 +2846,7 @@ API Changes (:issue:`5339`) - default for `display.max_seq_len` is now 100 rather then `None`. This activates truncated display ("...") of long sequences in various places. (:issue:`3391`) -- **All** division with ``NDFrame`` - likes is now truedivision, regardless +- **All** division with ``NDFrame`` - likes is now true division, regardless of the future import. You can use ``//`` and ``floordiv`` to do integer division. @@ -2787,10 +2868,10 @@ API Changes dtype: float64 - raise/warn ``SettingWithCopyError/Warning`` exception/warning when setting of a - copy thru chained assignment is detected, settable via option ``mode.chained_assignment`` + copy through chained assignment is detected, settable via option ``mode.chained_assignment`` - test the list of ``NA`` values in the csv parser. add ``N/A``, ``#NA`` as independent default na values (:issue:`5521`) -- The refactoring involving``Series`` deriving from ``NDFrame`` breaks ``rpy2<=2.3.8``. an Issue +- The refactoring involving ``Series`` deriving from ``NDFrame`` breaks ``rpy2<=2.3.8``. an Issue has been opened against rpy2 and a workaround is detailed in :issue:`5698`. Thanks @JanSchulz. - ``Series.argmin`` and ``Series.argmax`` are now aliased to ``Series.idxmin`` and ``Series.idxmax``. These return the *index* of the min or max element respectively. Prior to 0.13.0 these would return @@ -2888,7 +2969,7 @@ See :ref:`Internal Refactoring` (datetime/timedelta/time etc.) into a separate, cleaned up wrapper class. (:issue:`4613`) - Complex compat for ``Series`` with ``ndarray``. (:issue:`4819`) -- Removed unnecessary ``rwproperty`` from codebase in favor of builtin +- Removed unnecessary ``rwproperty`` from code base in favor of builtin property. (:issue:`4843`) - Refactor object level numeric methods (mean/sum/min/max...) from object level modules to ``core/generic.py`` (:issue:`4435`). @@ -2932,7 +3013,7 @@ Bug Fixes - A zero length series written in Fixed format not deserializing properly. (:issue:`4708`) - Fixed decoding perf issue on pyt3 (:issue:`5441`) - - Validate levels in a multi-index before storing (:issue:`5527`) + - Validate levels in a MultiIndex before storing (:issue:`5527`) - Correctly handle ``data_columns`` with a Panel (:issue:`5717`) - Fixed bug in tslib.tz_convert(vals, tz1, tz2): it could raise IndexError exception while trying to access trans[pos + 1] (:issue:`4496`) @@ -2995,7 +3076,7 @@ Bug Fixes alignment (:issue:`3777`) - frozenset objects now raise in the ``Series`` constructor (:issue:`4482`, :issue:`4480`) -- Fixed issue with sorting a duplicate multi-index that has multiple dtypes +- Fixed issue with sorting a duplicate MultiIndex that has multiple dtypes (:issue:`4516`) - Fixed bug in ``DataFrame.set_values`` which was causing name attributes to be lost when expanding the index. (:issue:`3742`, :issue:`4039`) @@ -3014,7 +3095,7 @@ Bug Fixes - Fix boolean indexing on an empty series loses index names (:issue:`4235`), infer_dtype works with empty arrays. - Fix reindexing with multiple axes; if an axes match was not replacing the - current axes, leading to a possible lazay frequency inference issue + current axes, leading to a possible lazy frequency inference issue (:issue:`3317`) - Fixed issue where ``DataFrame.apply`` was reraising exceptions incorrectly (causing the original stack trace to be truncated). @@ -3036,17 +3117,17 @@ Bug Fixes (:issue:`4727`) - Fix some inconsistencies with ``Index.rename`` and ``MultiIndex.rename``, etc. (:issue:`4718`, :issue:`4628`) -- Bug in using ``iloc/loc`` with a cross-sectional and duplicate indicies +- Bug in using ``iloc/loc`` with a cross-sectional and duplicate indices (:issue:`4726`) - Bug with using ``QUOTE_NONE`` with ``to_csv`` causing ``Exception``. (:issue:`4328`) - Bug with Series indexing not raising an error when the right-hand-side has an incorrect length (:issue:`2702`) -- Bug in multi-indexing with a partial string selection as one part of a +- Bug in MultiIndexing with a partial string selection as one part of a MultIndex (:issue:`4758`) - Bug with reindexing on the index with a non-unique index will now raise ``ValueError`` (:issue:`4746`) -- Bug in setting with ``loc/ix`` a single indexer with a multi-index axis and +- Bug in setting with ``loc/ix`` a single indexer with a MultiIndex axis and a NumPy array, related to (:issue:`3777`) - Bug in concatenation with duplicate columns across dtypes not merging with axis=0 (:issue:`4771`, :issue:`4975`) @@ -3117,7 +3198,7 @@ Bug Fixes - Make sure series-series boolean comparisons are label based (:issue:`4947`) - Bug in multi-level indexing with a Timestamp partial indexer (:issue:`4294`) -- Tests/fix for multi-index construction of an all-nan frame (:issue:`4078`) +- Tests/fix for MultiIndex construction of an all-nan frame (:issue:`4078`) - Fixed a bug where :func:`~pandas.read_html` wasn't correctly inferring values of tables with commas (:issue:`5029`) - Fixed a bug where :func:`~pandas.read_html` wasn't providing a stable @@ -3171,10 +3252,10 @@ Bug Fixes - Fixed bug in Excel writers where frames with duplicate column names weren't written correctly. (:issue:`5235`) - Fixed issue with ``drop`` and a non-unique index on Series (:issue:`5248`) -- Fixed seg fault in C parser caused by passing more names than columns in +- Fixed segfault in C parser caused by passing more names than columns in the file. (:issue:`5156`) - Fix ``Series.isin`` with date/time-like dtypes (:issue:`5021`) -- C and Python Parser can now handle the more common multi-index column +- C and Python Parser can now handle the more common MultiIndex column format which doesn't have a row for index names (:issue:`4702`) - Bug when trying to use an out-of-bounds date as an object dtype (:issue:`5312`) @@ -3199,7 +3280,7 @@ Bug Fixes - performance improvements in ``isnull`` on larger size pandas objects - Fixed various setitem with 1d ndarray that does not have a matching length to the indexer (:issue:`5508`) -- Bug in getitem with a multi-index and ``iloc`` (:issue:`5528`) +- Bug in getitem with a MultiIndex and ``iloc`` (:issue:`5528`) - Bug in delitem on a Series (:issue:`5542`) - Bug fix in apply when using custom function and objects are not mutated (:issue:`5545`) - Bug in selecting from a non-unique index with ``loc`` (:issue:`5553`) @@ -3208,7 +3289,7 @@ Bug Fixes - Bug in repeated indexing of object with resultant non-unique index (:issue:`5678`) - Bug in fillna with Series and a passed series/dict (:issue:`5703`) - Bug in groupby transform with a datetime-like grouper (:issue:`5712`) -- Bug in multi-index selection in PY3 when using certain keys (:issue:`5725`) +- Bug in MultiIndex selection in PY3 when using certain keys (:issue:`5725`) - Row-wise concat of differing dtypes failing in certain cases (:issue:`5754`) pandas 0.12.0 @@ -3229,14 +3310,14 @@ New Features - Added module for reading and writing Stata files: pandas.io.stata (:issue:`1512`) includes ``to_stata`` DataFrame method, and a ``read_stata`` top-level reader - Added support for writing in ``to_csv`` and reading in ``read_csv``, - multi-index columns. The ``header`` option in ``read_csv`` now accepts a + MultiIndex columns. The ``header`` option in ``read_csv`` now accepts a list of the rows from which to read the index. Added the option, ``tupleize_cols`` to provide compatibility for the pre 0.12 behavior of - writing and reading multi-index columns via a list of tuples. The default in + writing and reading MultiIndex columns via a list of tuples. The default in 0.12 is to write lists of tuples and *not* interpret list of tuples as a - multi-index column. + MultiIndex column. Note: The default value will change in 0.12 to make the default *to* write and - read multi-index columns in the new format. (:issue:`3571`, :issue:`1651`, :issue:`3141`) + read MultiIndex columns in the new format. (:issue:`3571`, :issue:`1651`, :issue:`3141`) - Add iterator to ``Series.str`` (:issue:`3638`) - ``pd.set_option()`` now allows N option, value pairs (:issue:`3667`). - Added keyword parameters for different types of scatter_matrix subplots @@ -3377,7 +3458,7 @@ API Changes - more consistency in the to_datetime return types (give string/array of string inputs) (:issue:`3888`) - The internal ``pandas`` class hierarchy has changed (slightly). The previous ``PandasObject`` now is called ``PandasContainer`` and a new - ``PandasObject`` has become the baseclass for ``PandasContainer`` as well + ``PandasObject`` has become the base class for ``PandasContainer`` as well as ``Index``, ``Categorical``, ``GroupBy``, ``SparseList``, and ``SparseArray`` (+ their base classes). Currently, ``PandasObject`` provides string methods (from ``StringMixin``). (:issue:`4090`, :issue:`4092`) @@ -3447,7 +3528,7 @@ Bug Fixes - Fixed bug with ``Panel.transpose`` argument aliases (:issue:`3556`) - Fixed platform bug in ``PeriodIndex.take`` (:issue:`3579`) - Fixed bud in incorrect conversion of datetime64[ns] in ``combine_first`` (:issue:`3593`) -- Fixed bug in reset_index with ``NaN`` in a multi-index (:issue:`3586`) +- Fixed bug in reset_index with ``NaN`` in a MultiIndex (:issue:`3586`) - ``fillna`` methods now raise a ``TypeError`` when the ``value`` parameter is a ``list`` or ``tuple``. - Fixed bug where a time-series was being selected in preference to an actual column name @@ -3480,7 +3561,7 @@ Bug Fixes their first argument (:issue:`3702`) - Fix file tokenization error with \r delimiter and quoted fields (:issue:`3453`) - Groupby transform with item-by-item not upcasting correctly (:issue:`3740`) -- Incorrectly read a HDFStore multi-index Frame with a column specification (:issue:`3748`) +- Incorrectly read a HDFStore MultiIndex Frame with a column specification (:issue:`3748`) - ``read_html`` now correctly skips tests (:issue:`3741`) - PandasObjects raise TypeError when trying to hash (:issue:`3882`) - Fix incorrect arguments passed to concat that are not list-like (e.g. concat(df1,df2)) (:issue:`3481`) @@ -3497,7 +3578,7 @@ Bug Fixes - csv parsers would loop infinitely if ``iterator=True`` but no ``chunksize`` was specified (:issue:`3967`), Python parser failing with ``chunksize=1`` - Fix index name not propagating when using ``shift`` -- Fixed dropna=False being ignored with multi-index stack (:issue:`3997`) +- Fixed dropna=False being ignored with MultiIndex stack (:issue:`3997`) - Fixed flattening of columns when renaming MultiIndex columns DataFrame (:issue:`4004`) - Fix ``Series.clip`` for datetime series. NA/NaN threshold values will now throw ValueError (:issue:`3996`) - Fixed insertion issue into DataFrame, after rename (:issue:`4032`) @@ -3521,7 +3602,7 @@ Bug Fixes iterated over when regex=False (:issue:`4115`) - Fixed bug in ``convert_objects(convert_numeric=True)`` where a mixed numeric and object Series/Frame was not converting properly (:issue:`4119`) -- Fixed bugs in multi-index selection with column multi-index and duplicates +- Fixed bugs in MultiIndex selection with column MultiIndex and duplicates (:issue:`4145`, :issue:`4146`) - Fixed bug in the parsing of microseconds when using the ``format`` argument in ``to_datetime`` (:issue:`4152`) @@ -3729,7 +3810,7 @@ Bug Fixes - Bug in value_counts of ``datetime64[ns]`` Series (:issue:`3002`) - Fixed printing of ``NaT`` in an index - Bug in idxmin/idxmax of ``datetime64[ns]`` Series with ``NaT`` (:issue:`2982`) -- Bug in ``icol, take`` with negative indicies was producing incorrect return +- Bug in ``icol, take`` with negative indices was producing incorrect return values (see :issue:`2922`, :issue:`2892`), also check for out-of-bounds indices (:issue:`3029`) - Bug in DataFrame column insertion when the column creation fails, existing frame is left in an irrecoverable state (:issue:`3010`) @@ -3752,7 +3833,7 @@ Bug Fixes - Fix upsampling bug with closed='left' and daily to daily data (:issue:`3020`) - Fixed missing tick bars on scatter_matrix plot (:issue:`3063`) - Fixed bug in Timestamp(d,tz=foo) when d is date() rather then datetime() (:issue:`2993`) -- series.plot(kind='bar') now respects pylab color schem (:issue:`3115`) +- series.plot(kind='bar') now respects pylab color scheme (:issue:`3115`) - Fixed bug in reshape if not passed correct input, now raises TypeError (:issue:`2719`) - Fixed a bug where Series ctor did not respect ordering if OrderedDict passed in (:issue:`3282`) - Fix NameError issue on RESO_US (:issue:`2787`) @@ -3790,7 +3871,7 @@ Bug Fixes a simple index (:issue:`2893`) - Fix Python ASCII file parsing when integer falls outside of floating point spacing (:issue:`3258`) -- fixed pretty priniting of sets (:issue:`3294`) +- fixed pretty printing of sets (:issue:`3294`) - Panel() and Panel.from_dict() now respects ordering when give OrderedDict (:issue:`3303`) - DataFrame where with a datetimelike incorrectly selecting (:issue:`3311`) - Ensure index casts work even in Int64Index @@ -3830,14 +3911,14 @@ Improvements to existing features - ``HDFStore`` - - enables storing of multi-index dataframes (closes :issue:`1277`) + - enables storing of MultiIndex dataframes (closes :issue:`1277`) - support data column indexing and selection, via ``data_columns`` keyword in append - support write chunking to reduce memory footprint, via ``chunksize`` keyword to append - support automagic indexing via ``index`` keyword to append - support ``expectedrows`` keyword in append to inform ``PyTables`` about - the expected tablesize + the expected table size - support ``start`` and ``stop`` keywords in select to limit the row selection space - added ``get_store`` context manager to automatically import with pandas @@ -3908,7 +3989,7 @@ Bug Fixes - Fix setitem on a Series with a boolean key and a non-scalar as value (:issue:`2686`) - Box datetime64 values in Series.apply/map (:issue:`2627`, :issue:`2689`) -- Upconvert datetime + datetime64 values when concatenating frames (:issue:`2624`) +- Up convert datetime + datetime64 values when concatenating frames (:issue:`2624`) - Raise a more helpful error message in merge operations when one DataFrame has duplicate columns (:issue:`2649`) - Fix partial date parsing issue occurring only when code is run at EOM @@ -4115,7 +4196,7 @@ Bug Fixes datetime64 when calling DataFrame.apply. (:issue:`2374`) - Raise exception when calling to_panel on non uniquely-indexed frame (:issue:`2441`) - Improved detection of console encoding on IPython zmq frontends (:issue:`2458`) -- Preserve time zone when .append-ing two time series (:issue:`2260`) +- Preserve time zone when .appending two time series (:issue:`2260`) - Box timestamps when calling reset_index on time-zone-aware index rather than creating a tz-less datetime64 column (:issue:`2262`) - Enable searching non-string columns in DataFrame.filter(like=...) (:issue:`2467`) @@ -4359,7 +4440,7 @@ Bug Fixes - Fix DatetimeIndex.isin to function properly (:issue:`1763`) - Fix conversion of array of tz-aware datetime.datetime to DatetimeIndex with right time zone (:issue:`1777`) -- Fix DST issues with generating ancxhored date ranges (:issue:`1778`) +- Fix DST issues with generating anchored date ranges (:issue:`1778`) - Fix issue calling sort on result of Series.unique (:issue:`1807`) - Fix numerical issue leading to square root of negative number in rolling_std (:issue:`1840`) @@ -4612,14 +4693,14 @@ New Features - Add keys() method on DataFrame (:issue:`1240`) - Add new ``match`` function to API (similar to R) (:issue:`502`) - Add dayfirst option to parsers (:issue:`854`) -- Add ``method`` argument to ``align`` method for forward/backward fillin +- Add ``method`` argument to ``align`` method for forward/backward filling (:issue:`216`) - Add Panel.transpose method for rearranging axes (:issue:`695`) - Add new ``cut`` function (patterned after R) for discretizing data into equal range-length bins or arbitrary breaks of your choosing (:issue:`415`) - Add new ``qcut`` for cutting with quantiles (:issue:`1378`) - Add ``value_counts`` top level array method (:issue:`1392`) -- Added Andrews curves plot tupe (:issue:`1325`) +- Added Andrews curves plot type (:issue:`1325`) - Add lag plot (:issue:`1440`) - Add autocorrelation_plot (:issue:`1425`) - Add support for tox and Travis CI (:issue:`1382`) @@ -4690,7 +4771,7 @@ API Changes - Remove deprecated DataMatrix name - Default merge suffixes for overlap now have underscores instead of periods to facilitate tab completion, etc. (:issue:`1239`) -- Deprecation of offset, time_rule timeRule parameters throughout codebase +- Deprecation of offset, time_rule timeRule parameters throughout code base - Series.append and DataFrame.append no longer check for duplicate indexes by default, add verify_integrity parameter (:issue:`1394`) - Refactor Factor class, old constructor moved to Factor.from_array @@ -4879,7 +4960,7 @@ Bug Fixes - Fix combineAdd NotImplementedError for SparseDataFrame (:issue:`887`) - Fix DataFrame.to_html encoding and columns (:issue:`890`, :issue:`891`, :issue:`909`) - Fix na-filling handling in mixed-type DataFrame (:issue:`910`) -- Fix to DataFrame.set_value with non-existant row/col (:issue:`911`) +- Fix to DataFrame.set_value with non-existent row/col (:issue:`911`) - Fix malformed block in groupby when excluding nuisance columns (:issue:`916`) - Fix inconsistent NA handling in dtype=object arrays (:issue:`925`) - Fix missing center-of-mass computation in ewmcov (:issue:`862`) @@ -4935,7 +5016,7 @@ Bug Fixes - Fix indexing operation for floating point values (:issue:`780`, :issue:`798`) - Fix groupby case resulting in malformed dataframe (:issue:`814`) - Fix behavior of reindex of Series dropping name (:issue:`812`) -- Improve on redudant groupby computation (:issue:`775`) +- Improve on redundant groupby computation (:issue:`775`) - Catch possible NA assignment to int/bool series with exception (:issue:`839`) pandas 0.7.0 @@ -5116,7 +5197,7 @@ Bug Fixes - Raise exception in out-of-bounds indexing of Series instead of seg-faulting, regression from earlier releases (:issue:`495`) - Fix error when joining DataFrames of different dtypes within the same - typeclass (e.g. float32 and float64) (:issue:`486`) + type class (e.g. float32 and float64) (:issue:`486`) - Fix bug in Series.min/Series.max on objects like datetime.datetime (GH :issue:`487`) - Preserve index names in Index.union (:issue:`501`) @@ -5162,7 +5243,7 @@ Bug Fixes - Format floats to default to same number of digits (:issue:`395`) - Added decorator to copy docstring from one function to another (:issue:`449`) - Fix error in monotonic many-to-one left joins -- Fix __eq__ comparison between DateOffsets with different relativedelta +- Fix __eq__ comparison between DateOffsets with different relative delta keywords passed - Fix exception caused by parser converter returning strings (:issue:`583`) - Fix MultiIndex formatting bug with integer names (:issue:`601`) @@ -5461,7 +5542,7 @@ Improvements to existing features `Series.map` significantly when passed elementwise Python function, motivated by :issue:`355` - Cythonized `cache_readonly`, resulting in substantial micro-performance - enhancements throughout the codebase (:issue:`361`) + enhancements throughout the code base (:issue:`361`) - Special Cython matrix iterator for applying arbitrary reduction operations with 3-5x better performance than `np.apply_along_axis` (:issue:`309`) - Add `raw` option to `DataFrame.apply` for getting better performance when @@ -5751,7 +5832,7 @@ pandas 0.4.3 **Release date:** 10/9/2011 -This is largely a bugfix release from 0.4.2 but also includes a handful of new +This is largely a bug fix release from 0.4.2 but also includes a handful of new and enhanced features. Also, pandas can now be installed and used on Python 3 (thanks Thomas Kluyver!). @@ -5803,7 +5884,7 @@ Bug Fixes - Fix Python ndarray access in Cython code for sparse blocked index integrity check - Fix bug writing Series to CSV in Python 3 (:issue:`209`) -- Miscellaneous Python 3 bugfixes +- Miscellaneous Python 3 bug fixes Thanks ~~~~~~ @@ -5828,7 +5909,7 @@ New Features int64-based time series (e.g. using NumPy's datetime64 one day) and also faster operations on DataFrame objects storing record array-like data. - Refactored `Index` classes to have a `join` method and associated data - alignment routines throughout the codebase to be able to leverage optimized + alignment routines throughout the code base to be able to leverage optimized joining / merging routines. - Added `Series.align` method for aligning two series with choice of join method @@ -6164,7 +6245,7 @@ API Changes - Removed `pandas.core.pytools` module. Code has been moved to `pandas.core.common` - Tacked on `groupName` attribute for groups in GroupBy renamed to `name` -- Panel/LongPanel `dims` attribute renamed to `shape` to be more conformant +- Panel/LongPanel `dims` attribute renamed to `shape` to be more conforming - Slicing a `Series` returns a view now - More Series deprecations / renaming: `toCSV` to `to_csv`, `asOf` to `asof`, `merge` to `map`, `applymap` to `apply`, `toDict` to `to_dict`, diff --git a/doc/source/reshaping.rst b/doc/source/reshaping.rst index 250a1808e496e..7d9925d800441 100644 --- a/doc/source/reshaping.rst +++ b/doc/source/reshaping.rst @@ -106,12 +106,12 @@ Closely related to the :meth:`~DataFrame.pivot` method are the related ``MultiIndex`` objects (see the section on :ref:`hierarchical indexing `). Here are essentially what these methods do: - - ``stack``: "pivot" a level of the (possibly hierarchical) column labels, - returning a ``DataFrame`` with an index with a new inner-most level of row - labels. - - ``unstack``: (inverse operation of ``stack``) "pivot" a level of the - (possibly hierarchical) row index to the column axis, producing a reshaped - ``DataFrame`` with a new inner-most level of column labels. +* ``stack``: "pivot" a level of the (possibly hierarchical) column labels, + returning a ``DataFrame`` with an index with a new inner-most level of row + labels. +* ``unstack``: (inverse operation of ``stack``) "pivot" a level of the + (possibly hierarchical) row index to the column axis, producing a reshaped + ``DataFrame`` with a new inner-most level of column labels. .. image:: _static/reshaping_unstack.png @@ -132,8 +132,8 @@ from the hierarchical indexing section: The ``stack`` function "compresses" a level in the ``DataFrame``'s columns to produce either: - - A ``Series``, in the case of a simple column Index. - - A ``DataFrame``, in the case of a ``MultiIndex`` in the columns. +* A ``Series``, in the case of a simple column Index. +* A ``DataFrame``, in the case of a ``MultiIndex`` in the columns. If the columns have a ``MultiIndex``, you can choose which level to stack. The stacked level becomes the new lowest level in a ``MultiIndex`` on the columns: @@ -351,13 +351,13 @@ strategies. It takes a number of arguments: -- ``data``: a DataFrame object. -- ``values``: a column or a list of columns to aggregate. -- ``index``: a column, Grouper, array which has the same length as data, or list of them. +* ``data``: a DataFrame object. +* ``values``: a column or a list of columns to aggregate. +* ``index``: a column, Grouper, array which has the same length as data, or list of them. Keys to group by on the pivot table index. If an array is passed, it is being used as the same manner as column values. -- ``columns``: a column, Grouper, array which has the same length as data, or list of them. +* ``columns``: a column, Grouper, array which has the same length as data, or list of them. Keys to group by on the pivot table column. If an array is passed, it is being used as the same manner as column values. -- ``aggfunc``: function to use for aggregation, defaulting to ``numpy.mean``. +* ``aggfunc``: function to use for aggregation, defaulting to ``numpy.mean``. Consider a data set like this: @@ -431,17 +431,17 @@ unless an array of values and an aggregation function are passed. It takes a number of arguments -- ``index``: array-like, values to group by in the rows. -- ``columns``: array-like, values to group by in the columns. -- ``values``: array-like, optional, array of values to aggregate according to +* ``index``: array-like, values to group by in the rows. +* ``columns``: array-like, values to group by in the columns. +* ``values``: array-like, optional, array of values to aggregate according to the factors. -- ``aggfunc``: function, optional, If no values array is passed, computes a +* ``aggfunc``: function, optional, If no values array is passed, computes a frequency table. -- ``rownames``: sequence, default ``None``, must match number of row arrays passed. -- ``colnames``: sequence, default ``None``, if passed, must match number of column +* ``rownames``: sequence, default ``None``, must match number of row arrays passed. +* ``colnames``: sequence, default ``None``, if passed, must match number of column arrays passed. -- ``margins``: boolean, default ``False``, Add row/column margins (subtotals) -- ``normalize``: boolean, {'all', 'index', 'columns'}, or {0,1}, default ``False``. +* ``margins``: boolean, default ``False``, Add row/column margins (subtotals) +* ``normalize``: boolean, {'all', 'index', 'columns'}, or {0,1}, default ``False``. Normalize by dividing all values by the sum of values. @@ -615,10 +615,10 @@ As with the ``Series`` version, you can pass values for the ``prefix`` and ``prefix_sep``. By default the column name is used as the prefix, and '_' as the prefix separator. You can specify ``prefix`` and ``prefix_sep`` in 3 ways: -- string: Use the same value for ``prefix`` or ``prefix_sep`` for each column +* string: Use the same value for ``prefix`` or ``prefix_sep`` for each column to be encoded. -- list: Must be the same length as the number of columns being encoded. -- dict: Mapping column name to prefix. +* list: Must be the same length as the number of columns being encoded. +* dict: Mapping column name to prefix. .. ipython:: python @@ -654,7 +654,7 @@ When a column contains only one level, it will be omitted in the result. pd.get_dummies(df, drop_first=True) By default new columns will have ``np.uint8`` dtype. -To choose another dtype, use the``dtype`` argument: +To choose another dtype, use the ``dtype`` argument: .. ipython:: python diff --git a/doc/source/sparse.rst b/doc/source/sparse.rst index 260d8aa32ef52..884512981e1c9 100644 --- a/doc/source/sparse.rst +++ b/doc/source/sparse.rst @@ -62,6 +62,26 @@ Any sparse object can be converted back to the standard dense form by calling sts.to_dense() +.. _sparse.accessor: + +Sparse Accessor +--------------- + +.. versionadded:: 0.24.0 + +Pandas provides a ``.sparse`` accessor, similar to ``.str`` for string data, ``.cat`` +for categorical data, and ``.dt`` for datetime-like data. This namespace provides +attributes and methods that are specific to sparse data. + +.. ipython:: python + + s = pd.Series([0, 0, 1, 2], dtype="Sparse[int]") + s.sparse.density + s.sparse.fill_value + +This accessor is available only on data with ``SparseDtype``, and on the :class:`Series` +class itself for creating a Series with sparse data from a scipy COO matrix with. + .. _sparse.array: SparseArray @@ -104,9 +124,9 @@ Sparse data should have the same dtype as its dense representation. Currently, ``float64``, ``int64`` and ``bool`` dtypes are supported. Depending on the original dtype, ``fill_value`` default changes: -- ``float64``: ``np.nan`` -- ``int64``: ``0`` -- ``bool``: ``False`` +* ``float64``: ``np.nan`` +* ``int64``: ``0`` +* ``bool``: ``False`` .. ipython:: python diff --git a/doc/source/spelling_wordlist.txt b/doc/source/spelling_wordlist.txt new file mode 100644 index 0000000000000..be93cdad083e9 --- /dev/null +++ b/doc/source/spelling_wordlist.txt @@ -0,0 +1,920 @@ +IPython +ipython +numpy +NumPy +Reindexing +reindexing +ga +fe +reindexed +automagic +closedness +ae +arbitrarly +losslessly +Histogramming +histogramming +concat +resampling +iterables +sparsified +df +loc +gc +Timeseries +ndarrays +ndarray +dtype +dtypes +dtyped +reindex +sliceable +timedelta +Timedeltas +timedeltas +subpackages +subpackage +filepath +io +nthreads +kwargs +kwarg +arg +args +Datetimelike +datetime +datetimes +tz +builtin +NaN +nan +behaviour +quantiling +aggregators +aggregator +Dtypes +groupby +GroupBy +Tablewise +Elementwise +ufunc +ufuncs +dict +namedtuples +namedtuple +iterrows +upcasted +upcasting +upcast +searchsorted +downcasting +Likert +categoricals +Groupby +Unioning +csv +Upcase +resampling +Upcase +Lowcase +Propcase +Interop +Stata +stata +bysort +Spearman +Wikipedia +debiasing +docstrings +docstring +Docstrings +autosummary +linting +toolchain +Appveyor +Akogun +online +pdf +reStructuredText +reST +backticks +cpus +str +idxmin +mins +agg +DataFrame +dataframes +NaT +len +Statsmodels +Bokeh +Protovis +Seaborn +Wickham +shareability +apps +app +Plotly +Spyder +Fama +Eurostat +organisations +Geopandas +Dask +Scikit +backends +Engarde +Cyberpandas +Accessor +Numba +optimising +Cython +cython +cythonizing +cythonized +Vectorize +ol +subclassing +IPv +iteritems +itertuples +dt +upcast +subsetting +programmatically +stderr +scipy +SparseArray +doctests +nd +refactored +Jit +stdout +Typeclass +Pythonic +zscore +SQL +broadcastable +resample +resamples +groupbys +metaprogramming +upcast +un +dropna +ints +int +boxplot +groupwise +indices +pre +datetimelike +dev +gd +colname +intemname +nd +isin +backporting +admin +Debian +Ubuntu +Centos +RHEL +xlsx +xz +ftp +impl +timespans +pre +Regex +regex +sortedness +delim +usecols +skipinitialspace +skiprows +skipfooter +nrows +na +iso +dayfirst +chunksize +gz +bz +lineterminator +quotechar +doublequote +escapechar +tupleize +prepended +colspecs +NONNUMERIC +serializer +localhost +json +strtod +deserialization +Hadoop +ns +stringified +xclip +xsel +gtk +gtpy +Msgpacks +msgpack +msgpacks +foo +ptrepack +sqlalchemy +sqlite +Sqlite +dta +bdat +netCDF +backend +deserialising +deserializing +qtpy +indexables +itemsize +de +sas +Miniconda +itemname +ndims +ndim +mergands +Timeseries +timeseries +asof +Nans +DataFrames +fillna +ffill +bfill +alignable +sim +py +ipy +colheader +yearfirst +repr +EngFormatter +frontends +frontend +longtable +multirow +cline +clines +colwidth +Sparsify +html +pprint +mathjax +Jupyter +xls +xlsm +hdf +numexpr +matplotlib +timedeltas +lexual +danbirken +isnull +Timestamp +np +xs +locs +datelike +dups +recarray +setitem +rhs +gaussian +kde +gkde +fwf +iNf +astyping +vbench +lgautier +jnothman +roundtrip +xlrd +buf +jtratner +tavistmorph +numpand +unserialiable +tseries +mul +completers +refactor +Refactor +subclassed +consolidatable +setitem +DataFrame +klass +jtratner +bs +lxml +rockg +inplace +pyt +tslib +vals +pos +cparser +locs +repr'd +cumsum +cumprod +rhs +datetimeindex +reraising +iloc +setitem +lhs +ticklocs +ticklabels +immerrr +np +kwds +travis +ci +yarikoptic +setitem +delitem +cpcloud +pprinting +hoechenberger +Faq +FAQ +faq +mtkini +spearman +SleepingPills +astypes +cov +timedeltalike +weekmasks +Weekmasks +xlrd +unioning +uint +iget +applymap +stonebig +recarrays +tdsmith +tokenization +google +xN +sharex +famafrench +strptime +stephenwlin +nans +diff +ohlc +util +seg +getitem +queryables +Dataframe +idxmax +putmasking +argsort +unsampling +pylab +fromordinal +andrews +strftime +wb +gzipped +gzip +aggfunc +multithreading +unicode +bork +tokenizer +sortlevel +Scikits +isnull +ndpanel +notnul +ctor +tzinfo +tzoffset +endianness +Upsampling +upsampling +upsampled +locators +locator +astimezone +iget +qcut +ewma +icol +printoption +quantileTS +UTC +utc +bool +init +OLS +Isnull +nansum +Cythonize +extlinks +utcoffset +khash +kendall +tolist +unhandled +downsampling +dayofyear +setops +discretizing +klib +ylabel +bday +BDay +timeRule +unmergeable +navar +pyplot +multiindex +combineAdd +ewmcov +algos +unpickling +MultiIndex +Memoize +Unbox +nanops +vectorize +DataFame +fallback +sharey +xlabel +notnull +asfreq +crit +rpy +nanvar +ddof +ols +printoptions +rankdata +pyo +camelCased +cacheable +unindexed +reduceat +blosc +aggregatable +idx +tradeoff +nPeriods +camelCasing +camelCased +LongPanel +truediv +px +parseCSV +unpivoted +extractall +weekofyear +dayofweek +CDay +Nano +parameterised +sunday +monday +tuesday +friday +upsample +resampled +tzfile +bools +xlsxwriter +ggplot +Colormaps +colormaps +trippable +callables +pivotting +GBQ +intersphinx +hashable +compat +Compat +rollforward +seekable +endian +subrecords +readonly +orderedness +eval +datetimelikes +pytables +argmax +argmin +utf +segfault +segfaults +xlims +CPython +MultiIndexed +blosc +blosclz +hc +lz +zlib +zstd +tput +boxplot +UInt +unioned +hashtable +saslib +resampled +dicts +datetimetz +ascii +evals +Compat +lexsorted +errstate +incompat +boxplots +honour +UTF +subclasse +ungrouped +xport +writeable +unencodable +serialising +serialise +Segfault +ceiled +xarray +jupyter +ified +isoformat +downsample +upsample +aggregator +ascii +compat +src +ness +unencoded +submethods +gbq +vectorised +nanos +Bigquery +complib +overridable +xlabels +xticklabels +listlike +jobComplete +cummin +cummax +undeprecated +triang +errored +unpickle +ngroups +multiindexes +xticks +yticks +errorbars +barplots +rcParams +dfs +nw +Openpyxl +barh +timestamp +inv +Welford +tarball +hdfstore +Pandonic +Perf +factorizer +sharey +yyyy +dd +xxx +bdays +nfrequencies +XYZ +Vytautas +Jancauskas +rankdata +Astype +astyped +mergesort +nano +unpickled +dataframe +serialised +serialisation +numpies +deserialize +hashtables +unpivoting +cubehelix +unparsable +fu +Unpivots +rownames +retbins +objs +sep +stubnames +expr +func +skipna +halflife +cond +ceil +fillchar +swapcased +deletechars +figsize +bw +xlabelsize +ftypes +ge +Unpivots +lsuffix +fname +fo +ftypes +rsuffix +sparsifying +tup +cls +nonunique +xrange +periodIndex +pytz +ctime +dst +localtime +proleptic +tzname +stddev +resampler +Resampler +searchpath +cmap +visualising +figsize +desc +Iterable +da +ta +CategoricalIndex +specialised +takeable +iter +upcase +Outlier +fontsize +pearson +corrwith +eq +ewm +floordiv +ftype +iat +typeR +slinear +krogh +akima +BPoly +isna +kurt +le +lt +ne +notna +nsmallest +Deutsche +Colormap +colorbar +silverman +gridsize +radd +rdiv +regexes +rfloordiv +rmod +rmul +rpow +rsub +rtruediv +RandomState +sem +quicksort +heapsort +organised +swapaxes +swaplevel +OAuth +defaultdict +tablename +HDFStore +appendable +searchable +serialisable +lzo +usepackage +booktabs +coereced +spellcheck +misspelt +rcl +multicolumns +gfc +automagically +fastparquet +brotli +sql +nullable +performant +lexsorted +tw +latin +StrL +tshift +basestring +DatetimeIndex +periodIndex +pydatetime +perioddelta +ExcelFile +noqa +deepcopy +Discretize +hasnans +nbytes +nlevels +DateOffset +stringr +orderable +IntervalIndex +versionadded +lexsort +droplevel +swaplevel +kurt +IGNORECASE +findall +isalnum +isalpha +isdecimal +isdigit +islower +isnumeric +isspace +istitle +isupper +ljust +lstrip +rfind +rindex +rpartition +rsplit +rstrip +startswith +deletechars +whitespaces +insecable +stringr +zfill +tshift +SparseSeries +isoweekday +isocalendar +fromtimestamp +dateutil +utcfromtimestamp +utcnow +utctimetuple +api +ExtensionArray +nbytes +abc +ABCMeta +Typecode +ExtensionDtype +biufcmMOSUV +accessor +CategoricalDtype +DataFrameGroupBy +Weekmask +walkthrough +wieldy +stubnames +unix +asian +Eg +recomputation +useQueryCache +LocalPath +fspath +params +datatypes +connectable +multirows +sparsify +parseable +TimedeltaIndex +baz +pathlib +radviz +axvline +xtick +unpivot +StataWriter +StataReader +IndexSlice +uuid +cellstyle +tablewise +rowwise +columnwise +env +fba +Regexp +sparsify +multiline +UnsupportedFunctionCall +UnsortedIndexError +PerformanceWarning +ParserWarning +ParserError +OutOfBoundsDatetime +EmptyDataError +DtypeWarning +crosstab +SeriesGroupBy +nunique +nlargest +Truthy +cumcount +ngroup +bdate +toordinal +julian +timetz +timetuple +freqstr +daysinmonth +asm +TimedeltaIndex +pytimedelta +autodetect +coords +endswith +SparseDataFrame +spmatrix +swapcase +rjust +ndarrary +regexs +ptp +imag +gca +keywors +intercalary +daysinmonth +divmod +autocorr +asobject +Argsorts +xrot +RangeIndex +PeriodIndex +qyear +timeries +scikits +fromDict +levshape +putmask +asi +repl \ No newline at end of file diff --git a/doc/source/style.ipynb b/doc/source/style.ipynb index 152ca90049bf1..6f66c1a9bf7f9 100644 --- a/doc/source/style.ipynb +++ b/doc/source/style.ipynb @@ -985,7 +985,10 @@ "- `vertical-align`\n", "- `white-space: nowrap`\n", "\n", - "Only CSS2 named colors and hex colors of the form `#rgb` or `#rrggbb` are currently supported." + "Only CSS2 named colors and hex colors of the form `#rgb` or `#rrggbb` are currently supported.\n", + "\n", + "The following pseudo CSS properties are also available to set excel specific style properties:\n", + "- `number-format`\n" ] }, { diff --git a/doc/source/text.rst b/doc/source/text.rst index 4af64d9f791cc..d01c48695d0d6 100644 --- a/doc/source/text.rst +++ b/doc/source/text.rst @@ -55,8 +55,8 @@ Since ``df.columns`` is an Index object, we can use the ``.str`` accessor df.columns.str.lower() These string methods can then be used to clean up the columns as needed. -Here we are removing leading and trailing whitespaces, lowercasing all names, -and replacing any remaining whitespaces with underscores: +Here we are removing leading and trailing white spaces, lower casing all names, +and replacing any remaining white spaces with underscores: .. ipython:: python @@ -270,12 +270,13 @@ For concatenation with a ``Series`` or ``DataFrame``, it is possible to align th the ``join``-keyword. .. ipython:: python + :okwarning: - u = pd.Series(['b', 'd', 'a', 'c'], index=[1, 3, 0, 2]) - s - u - s.str.cat(u) - s.str.cat(u, join='left') + u = pd.Series(['b', 'd', 'a', 'c'], index=[1, 3, 0, 2]) + s + u + s.str.cat(u) + s.str.cat(u, join='left') .. warning:: @@ -296,7 +297,7 @@ In particular, alignment also means that the different lengths do not need to co The same alignment can be used when ``others`` is a ``DataFrame``: .. ipython:: python - + f = d.loc[[3, 2, 1, 0], :] s f @@ -305,20 +306,21 @@ The same alignment can be used when ``others`` is a ``DataFrame``: Concatenating a Series and many objects into a Series ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -All one-dimensional list-likes can be arbitrarily combined in a list-like container (including iterators, ``dict``-views, etc.): +All one-dimensional list-likes can be combined in a list-like container (including iterators, ``dict``-views, etc.): .. ipython:: python s u - s.str.cat([u, pd.Index(u.values), ['A', 'B', 'C', 'D'], map(str, u.index)], na_rep='-') + s.str.cat([u.values, + u.index.astype(str).values], na_rep='-') All elements must match in length to the calling ``Series`` (or ``Index``), except those having an index if ``join`` is not None: .. ipython:: python v - s.str.cat([u, v, ['A', 'B', 'C', 'D']], join='outer', na_rep='-') + s.str.cat([u, v], join='outer', na_rep='-') If using ``join='right'`` on a list of ``others`` that contains different indexes, the union of these indexes will be used as the basis for the final concatenation: diff --git a/doc/source/timedeltas.rst b/doc/source/timedeltas.rst index 745810704f665..e602e45784f4a 100644 --- a/doc/source/timedeltas.rst +++ b/doc/source/timedeltas.rst @@ -363,6 +363,13 @@ or ``np.timedelta64`` objects. Passing ``np.nan/pd.NaT/nat`` will represent miss pd.TimedeltaIndex(['1 days', '1 days, 00:00:05', np.timedelta64(2,'D'), datetime.timedelta(days=2,seconds=2)]) +The string 'infer' can be passed in order to set the frequency of the index as the +inferred frequency upon creation: + +.. ipython:: python + + pd.TimedeltaIndex(['0 days', '10 days', '20 days'], freq='infer') + Generating Ranges of Time Deltas ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/timeseries.rst b/doc/source/timeseries.rst index 1b0cf86995a39..a52c80106f100 100644 --- a/doc/source/timeseries.rst +++ b/doc/source/timeseries.rst @@ -21,51 +21,59 @@ Time Series / Date functionality ******************************** -pandas has proven very successful as a tool for working with time series data, -especially in the financial data analysis space. Using the NumPy ``datetime64`` and ``timedelta64`` dtypes, -we have consolidated a large number of features from other Python libraries like ``scikits.timeseries`` as well as created +pandas contains extensive capabilities and features for working with time series data for all domains. +Using the NumPy ``datetime64`` and ``timedelta64`` dtypes, pandas has consolidated a large number of +features from other Python libraries like ``scikits.timeseries`` as well as created a tremendous amount of new functionality for manipulating time series data. -In working with time series data, we will frequently seek to: +For example, pandas supports: - - generate sequences of fixed-frequency dates and time spans - - conform or convert time series to a particular frequency - - compute "relative" dates based on various non-standard time increments - (e.g. 5 business days before the last business day of the year), or "roll" - dates forward or backward +Parsing time series information from various sources and formats -pandas provides a relatively compact and self-contained set of tools for -performing the above tasks. +.. ipython:: python -Create a range of dates: + dti = pd.to_datetime(['1/1/2018', np.datetime64('2018-01-01'), datetime(2018, 1, 1)]) + dti + +Generate sequences of fixed-frequency dates and time spans .. ipython:: python - # 72 hours starting with midnight Jan 1st, 2011 - rng = pd.date_range('1/1/2011', periods=72, freq='H') - rng[:5] + dti = pd.date_range('2018-01-01', periods=3, freq='H') + dti -Index pandas objects with dates: +Manipulating and converting date times with timezone information .. ipython:: python - ts = pd.Series(np.random.randn(len(rng)), index=rng) - ts.head() + dti = dti.tz_localize('UTC') + dti + dti.tz_convert('US/Pacific') -Change frequency and fill gaps: +Resampling or converting a time series to a particular frequency .. ipython:: python - # to 45 minute frequency and forward fill - converted = ts.asfreq('45Min', method='pad') - converted.head() + idx = pd.date_range('2018-01-01', periods=5, freq='H') + ts = pd.Series(range(len(idx)), index=idx) + ts + ts.resample('2H').mean() -Resample the series to a daily frequency: +Performing date and time arithmetic with absolute or relative time increments .. ipython:: python - # Daily means - ts.resample('D').mean() + friday = pd.Timestamp('2018-01-05') + friday.day_name() + # Add 1 day + saturday = friday + pd.Timedelta('1 day') + saturday.day_name() + # Add 1 business day (Friday --> Monday) + monday = friday + pd.tseries.offsets.BDay() + monday.day_name() + +pandas provides a relatively compact and self-contained set of tools for +performing the above tasks and more. .. _timeseries.overview: @@ -73,17 +81,54 @@ Resample the series to a daily frequency: Overview -------- -The following table shows the type of time-related classes pandas can handle and -how to create them. +pandas captures 4 general time related concepts: + +#. Date times: A specific date and time with timezone support. Similar to ``datetime.datetime`` from the standard library. +#. Time deltas: An absolute time duration. Similar to ``datetime.timedelta`` from the standard library. +#. Time spans: A span of time defined by a point in time and its associated frequency. +#. Date offsets: A relative time duration that respects calendar arithmetic. Similar to ``dateutil.relativedelta.relativedelta`` from the ``dateutil`` package. + +===================== ================= =================== ============================================ ======================================== +Concept Scalar Class Array Class pandas Data Type Primary Creation Method +===================== ================= =================== ============================================ ======================================== +Date times ``Timestamp`` ``DatetimeIndex`` ``datetime64[ns]`` or ``datetime64[ns, tz]`` ``to_datetime`` or ``date_range`` +Time deltas ``Timedelta`` ``TimedeltaIndex`` ``timedelta64[ns]`` ``to_timedelta`` or ``timedelta_range`` +Time spans ``Period`` ``PeriodIndex`` ``period[freq]`` ``Period`` or ``period_range`` +Date offsets ``DateOffset`` ``None`` ``None`` ``DateOffset`` +===================== ================= =================== ============================================ ======================================== + +For time series data, it's conventional to represent the time component in the index of a :class:`Series` or :class:`DataFrame` +so manipulations can be performed with respect to the time element. + +.. ipython:: python + + pd.Series(range(3), index=pd.date_range('2000', freq='D', periods=3)) -================= =============================== =================================================================== -Class Remarks How to create -================= =============================== =================================================================== -``Timestamp`` Represents a single timestamp ``to_datetime``, ``Timestamp`` -``DatetimeIndex`` Index of ``Timestamp`` ``to_datetime``, ``date_range``, ``bdate_range``, ``DatetimeIndex`` -``Period`` Represents a single time span ``Period`` -``PeriodIndex`` Index of ``Period`` ``period_range``, ``PeriodIndex`` -================= =============================== =================================================================== +However, :class:`Series` and :class:`DataFrame` can directly also support the time component as data itself. + +.. ipython:: python + + pd.Series(pd.date_range('2000', freq='D', periods=3)) + +:class:`Series` and :class:`DataFrame` have extended data type support and functionality for ``datetime`` and ``timedelta`` +data when the time data is used as data itself. The ``Period`` and ``DateOffset`` data will be stored as ``object`` data. + +.. ipython:: python + + pd.Series(pd.period_range('1/1/2011', freq='M', periods=3)) + pd.Series(pd.date_range('1/1/2011', freq='M', periods=3)) + +Lastly, pandas represents null date times, time deltas, and time spans as ``NaT`` which +is useful for representing missing or null date like values and behaves similar +as ``np.nan`` does for float data. + +.. ipython:: python + + pd.Timestamp(pd.NaT) + pd.Timedelta(pd.NaT) + pd.Period(pd.NaT) + # Equality acts as np.nan would + pd.NaT == pd.NaT .. _timeseries.representation: @@ -185,6 +230,19 @@ options like ``dayfirst`` or ``format``, so use ``to_datetime`` if these are req pd.Timestamp('2010/11/12') +You can also use the ``DatetimeIndex`` constructor directly: + +.. ipython:: python + + pd.DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05']) + +The string 'infer' can be passed in order to set the frequency of the index as the +inferred frequency upon creation: + +.. ipython:: python + + pd.DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], freq='infer') + Providing a Format Argument ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -226,8 +284,8 @@ You can pass only the columns that you need to assemble. ``pd.to_datetime`` looks for standard designations of the datetime component in the column names, including: -- required: ``year``, ``month``, ``day`` -- optional: ``hour``, ``minute``, ``second``, ``millisecond``, ``microsecond``, ``nanosecond`` +* required: ``year``, ``month``, ``day`` +* optional: ``hour``, ``minute``, ``second``, ``millisecond``, ``microsecond``, ``nanosecond`` Invalid Data ~~~~~~~~~~~~ @@ -356,7 +414,7 @@ In practice this becomes very cumbersome because we often need a very long index with a large number of timestamps. If we need timestamps on a regular frequency, we can use the :func:`date_range` and :func:`bdate_range` functions to create a ``DatetimeIndex``. The default frequency for ``date_range`` is a -**calendar day** while the default for ``bdate_range`` is a **business day**: +**day** while the default for ``bdate_range`` is a **business day**: .. ipython:: python @@ -463,14 +521,14 @@ Indexing One of the main uses for ``DatetimeIndex`` is as an index for pandas objects. The ``DatetimeIndex`` class contains many time series related optimizations: - - A large range of dates for various offsets are pre-computed and cached - under the hood in order to make generating subsequent date ranges very fast - (just have to grab a slice). - - Fast shifting using the ``shift`` and ``tshift`` method on pandas objects. - - Unioning of overlapping ``DatetimeIndex`` objects with the same frequency is - very fast (important for fast data alignment). - - Quick access to date fields via properties such as ``year``, ``month``, etc. - - Regularization functions like ``snap`` and very fast ``asof`` logic. +* A large range of dates for various offsets are pre-computed and cached + under the hood in order to make generating subsequent date ranges very fast + (just have to grab a slice). +* Fast shifting using the ``shift`` and ``tshift`` method on pandas objects. +* Unioning of overlapping ``DatetimeIndex`` objects with the same frequency is + very fast (important for fast data alignment). +* Quick access to date fields via properties such as ``year``, ``month``, etc. +* Regularization functions like ``snap`` and very fast ``asof`` logic. ``DatetimeIndex`` objects have all the basic functionality of regular ``Index`` objects, and a smorgasbord of advanced time series specific methods for easy @@ -690,6 +748,34 @@ regularity will result in a ``DatetimeIndex``, although frequency is lost: ts2[[0, 2, 6]].index +.. _timeseries.iterating-label: + +Iterating through groups +------------------------ + +With the ``Resampler`` object in hand, iterating through the grouped data is very +natural and functions similarly to :py:func:`itertools.groupby`: + +.. ipython:: python + + small = pd.Series( + range(6), + index=pd.to_datetime(['2017-01-01T00:00:00', + '2017-01-01T00:30:00', + '2017-01-01T00:31:00', + '2017-01-01T01:00:00', + '2017-01-01T03:00:00', + '2017-01-01T03:05:00']) + ) + resampled = small.resample('H') + + for name, group in resampled: + print("Group: ", name) + print("-" * 27) + print(group, end="\n\n") + +See :ref:`groupby.iterating-label` or :class:`Resampler.__iter__` for more. + .. _timeseries.components: Time/Date Components @@ -711,6 +797,7 @@ There are several time/date properties that one can access from ``Timestamp`` or nanosecond,"The nanoseconds of the datetime" date,"Returns datetime.date (does not contain timezone information)" time,"Returns datetime.time (does not contain timezone information)" + timetz,"Returns datetime.time as local time with timezone information" dayofyear,"The ordinal day of year" weekofyear,"The week ordinal of the year" week,"The week ordinal of the year" @@ -797,11 +884,11 @@ We could have done the same thing with ``DateOffset``: The key features of a ``DateOffset`` object are: -- It can be added / subtracted to/from a datetime object to obtain a +* It can be added / subtracted to/from a datetime object to obtain a shifted date. -- It can be multiplied by an integer (positive or negative) so that the +* It can be multiplied by an integer (positive or negative) so that the increment will be applied multiple times. -- It has :meth:`~pandas.DateOffset.rollforward` and +* It has :meth:`~pandas.DateOffset.rollforward` and :meth:`~pandas.DateOffset.rollback` methods for moving a date forward or backward to the next or previous "offset date". @@ -833,25 +920,42 @@ It's definitely worth exploring the ``pandas.tseries.offsets`` module and the various docstrings for the classes. These operations (``apply``, ``rollforward`` and ``rollback``) preserve time -(hour, minute, etc) information by default. To reset time, use ``normalize=True`` -when creating the offset instance. If ``normalize=True``, the result is -normalized after the function is applied. - +(hour, minute, etc) information by default. To reset time, use ``normalize`` +before or after applying the operation (depending on whether you want the +time information included in the operation. .. ipython:: python + ts = pd.Timestamp('2014-01-01 09:00') day = Day() - day.apply(pd.Timestamp('2014-01-01 09:00')) - - day = Day(normalize=True) - day.apply(pd.Timestamp('2014-01-01 09:00')) + day.apply(ts) + day.apply(ts).normalize() + ts = pd.Timestamp('2014-01-01 22:00') hour = Hour() - hour.apply(pd.Timestamp('2014-01-01 22:00')) + hour.apply(ts) + hour.apply(ts).normalize() + hour.apply(pd.Timestamp("2014-01-01 23:30")).normalize() + +.. _timeseries.dayvscalendarday: + +Day vs. CalendarDay +~~~~~~~~~~~~~~~~~~~ + +:class:`Day` (``'D'``) is a timedelta-like offset that respects absolute time +arithmetic and is an alias for 24 :class:`Hour`. This offset is the default +argument to many pandas time related function like :func:`date_range` and :func:`timedelta_range`. + +:class:`CalendarDay` (``'CD'``) is a relativedelta-like offset that respects +calendar time arithmetic. :class:`CalendarDay` is useful preserving calendar day +semantics with date times with have day light savings transitions, i.e. :class:`CalendarDay` +will preserve the hour before the day light savings transition. - hour = Hour(normalize=True) - hour.apply(pd.Timestamp('2014-01-01 22:00')) - hour.apply(pd.Timestamp('2014-01-01 23:00')) +.. ipython:: python + + ts = pd.Timestamp('2016-10-30 00:00:00', tz='Europe/Helsinki') + ts + pd.offsets.Day(1) + ts + pd.offsets.CalendarDay(1) Parametric Offsets @@ -1144,7 +1248,8 @@ frequencies. We will refer to these aliases as *offset aliases*. "B", "business day frequency" "C", "custom business day frequency" - "D", "calendar day frequency" + "D", "day frequency" + "CD", "calendar day frequency" "W", "weekly frequency" "M", "month end frequency" "SM", "semi-month end frequency (15th and end of month)" @@ -1390,6 +1495,7 @@ the pandas objects. .. ipython:: python + ts = pd.Series(range(len(rng)), index=rng) ts = ts[:5] ts.shift(1) @@ -1750,7 +1856,7 @@ If ``Period`` freq is daily or higher (``D``, ``H``, ``T``, ``S``, ``L``, ``U``, ... ValueError: Input has different freq from Period(freq=H) -If ``Period`` has other freqs, only the same ``offsets`` can be added. Otherwise, ``ValueError`` will be raised. +If ``Period`` has other frequencies, only the same ``offsets`` can be added. Otherwise, ``ValueError`` will be raised. .. ipython:: python @@ -2064,9 +2170,9 @@ To supply the time zone, you can use the ``tz`` keyword to ``date_range`` and other functions. Dateutil time zone strings are distinguished from ``pytz`` time zones by starting with ``dateutil/``. -- In ``pytz`` you can find a list of common (and less common) time zones using +* In ``pytz`` you can find a list of common (and less common) time zones using ``from pytz import common_timezones, all_timezones``. -- ``dateutil`` uses the OS timezones so there isn't a fixed list available. For +* ``dateutil`` uses the OS timezones so there isn't a fixed list available. For common zones, the names are the same as ``pytz``. .. ipython:: python @@ -2156,8 +2262,8 @@ still considered to be equal even if they are in different time zones: rng_berlin[5] rng_eastern[5] == rng_berlin[5] -Like ``Series``, ``DataFrame``, and ``DatetimeIndex``, ``Timestamp``s can be converted to other -time zones using ``tz_convert``: +Like ``Series``, ``DataFrame``, and ``DatetimeIndex``; ``Timestamp`` objects +can be converted to other time zones using ``tz_convert``: .. ipython:: python @@ -2196,7 +2302,7 @@ To remove timezone from tz-aware ``DatetimeIndex``, use ``tz_localize(None)`` or didx.tz_convert(None) # tz_convert(None) is identical with tz_convert('UTC').tz_localize(None) - didx.tz_convert('UCT').tz_localize(None) + didx.tz_convert('UTC').tz_localize(None) .. _timeseries.timezone_ambiguous: @@ -2251,6 +2357,38 @@ constructor as well as ``tz_localize``. # tz_convert(None) is identical with tz_convert('UTC').tz_localize(None) didx.tz_convert('UCT').tz_localize(None) +.. _timeseries.timezone_nonexistent: + +Nonexistent Times when Localizing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A DST transition may also shift the local time ahead by 1 hour creating nonexistent +local times. The behavior of localizing a timeseries with nonexistent times +can be controlled by the ``nonexistent`` argument. The following options are available: + +* ``raise``: Raises a ``pytz.NonExistentTimeError`` (the default behavior) +* ``NaT``: Replaces nonexistent times with ``NaT`` +* ``shift``: Shifts nonexistent times forward to the closest real time + +.. ipython:: python + dti = date_range(start='2015-03-29 01:30:00', periods=3, freq='H') + # 2:30 is a nonexistent time + +Localization of nonexistent times will raise an error by default. + +.. code-block:: ipython + + In [2]: dti.tz_localize('Europe/Warsaw') + NonExistentTimeError: 2015-03-29 02:30:00 + +Transform nonexistent times to ``NaT`` or the closest real time forward in time. + +.. ipython:: python + dti + dti.tz_localize('Europe/Warsaw', nonexistent='shift') + dti.tz_localize('Europe/Warsaw', nonexistent='NaT') + + .. _timeseries.timezone_series: TZ Aware Dtypes diff --git a/doc/source/tutorials.rst b/doc/source/tutorials.rst index 895fe595de205..83c891c0c0e40 100644 --- a/doc/source/tutorials.rst +++ b/doc/source/tutorials.rst @@ -7,7 +7,7 @@ Tutorials This is a guide to many pandas tutorials, geared mainly for new users. Internal Guides ---------------- +=============== pandas' own :ref:`10 Minutes to pandas<10min>`. @@ -15,6 +15,9 @@ More complex recipes are in the :ref:`Cookbook`. A handy pandas `cheat sheet `_. +Community Guides +================ + pandas Cookbook --------------- @@ -28,33 +31,33 @@ repository `_. To run the examples in th clone the GitHub repository and get IPython Notebook running. See `How to use this cookbook `_. -- `A quick tour of the IPython Notebook: `_ +* `A quick tour of the IPython Notebook: `_ Shows off IPython's awesome tab completion and magic functions. -- `Chapter 1: `_ +* `Chapter 1: `_ Reading your data into pandas is pretty much the easiest thing. Even when the encoding is wrong! -- `Chapter 2: `_ +* `Chapter 2: `_ It's not totally obvious how to select data from a pandas dataframe. Here we explain the basics (how to take slices and get columns) -- `Chapter 3: `_ +* `Chapter 3: `_ Here we get into serious slicing and dicing and learn how to filter dataframes in complicated ways, really fast. -- `Chapter 4: `_ +* `Chapter 4: `_ Groupby/aggregate is seriously my favorite thing about pandas and I use it all the time. You should probably read this. -- `Chapter 5: `_ +* `Chapter 5: `_ Here you get to find out if it's cold in Montreal in the winter (spoiler: yes). Web scraping with pandas is fun! Here we combine dataframes. -- `Chapter 6: `_ +* `Chapter 6: `_ Strings with pandas are great. It has all these vectorized string operations and they're the best. We will turn a bunch of strings containing "Snow" into vectors of numbers in a trice. -- `Chapter 7: `_ +* `Chapter 7: `_ Cleaning up messy data is never a joy, but with pandas it's easier. -- `Chapter 8: `_ +* `Chapter 8: `_ Parsing Unix timestamps is confusing at first but it turns out to be really easy. -- `Chapter 9: `_ +* `Chapter 9: `_ Reading data from SQL databases. @@ -63,54 +66,54 @@ Lessons for new pandas users For more resources, please visit the main `repository `__. -- `01 - Lesson: `_ - - Importing libraries - - Creating data sets - - Creating data frames - - Reading from CSV - - Exporting to CSV - - Finding maximums - - Plotting data +* `01 - Lesson: `_ + * Importing libraries + * Creating data sets + * Creating data frames + * Reading from CSV + * Exporting to CSV + * Finding maximums + * Plotting data -- `02 - Lesson: `_ - - Reading from TXT - - Exporting to TXT - - Selecting top/bottom records - - Descriptive statistics - - Grouping/sorting data +* `02 - Lesson: `_ + * Reading from TXT + * Exporting to TXT + * Selecting top/bottom records + * Descriptive statistics + * Grouping/sorting data -- `03 - Lesson: `_ - - Creating functions - - Reading from EXCEL - - Exporting to EXCEL - - Outliers - - Lambda functions - - Slice and dice data +* `03 - Lesson: `_ + * Creating functions + * Reading from EXCEL + * Exporting to EXCEL + * Outliers + * Lambda functions + * Slice and dice data -- `04 - Lesson: `_ - - Adding/deleting columns - - Index operations +* `04 - Lesson: `_ + * Adding/deleting columns + * Index operations -- `05 - Lesson: `_ - - Stack/Unstack/Transpose functions +* `05 - Lesson: `_ + * Stack/Unstack/Transpose functions -- `06 - Lesson: `_ - - GroupBy function +* `06 - Lesson: `_ + * GroupBy function -- `07 - Lesson: `_ - - Ways to calculate outliers +* `07 - Lesson: `_ + * Ways to calculate outliers -- `08 - Lesson: `_ - - Read from Microsoft SQL databases +* `08 - Lesson: `_ + * Read from Microsoft SQL databases -- `09 - Lesson: `_ - - Export to CSV/EXCEL/TXT +* `09 - Lesson: `_ + * Export to CSV/EXCEL/TXT -- `10 - Lesson: `_ - - Converting between different kinds of formats +* `10 - Lesson: `_ + * Converting between different kinds of formats -- `11 - Lesson: `_ - - Combining data from various sources +* `11 - Lesson: `_ + * Combining data from various sources Practical data analysis with Python @@ -119,13 +122,13 @@ Practical data analysis with Python This `guide `_ is a comprehensive introduction to the data analysis process using the Python data ecosystem and an interesting open dataset. There are four sections covering selected topics as follows: -- `Munging Data `_ +* `Munging Data `_ -- `Aggregating Data `_ +* `Aggregating Data `_ -- `Visualizing Data `_ +* `Visualizing Data `_ -- `Time Series `_ +* `Time Series `_ .. _tutorial-exercises-new-users: @@ -134,25 +137,25 @@ Exercises for new users Practice your skills with real data sets and exercises. For more resources, please visit the main `repository `__. -- `01 - Getting & Knowing Your Data `_ +* `01 - Getting & Knowing Your Data `_ -- `02 - Filtering & Sorting `_ +* `02 - Filtering & Sorting `_ -- `03 - Grouping `_ +* `03 - Grouping `_ -- `04 - Apply `_ +* `04 - Apply `_ -- `05 - Merge `_ +* `05 - Merge `_ -- `06 - Stats `_ +* `06 - Stats `_ -- `07 - Visualization `_ +* `07 - Visualization `_ -- `08 - Creating Series and DataFrames `_ +* `08 - Creating Series and DataFrames `_ -- `09 - Time Series `_ +* `09 - Time Series `_ -- `10 - Deleting `_ +* `10 - Deleting `_ .. _tutorial-modern: @@ -164,29 +167,29 @@ Tutorial series written in 2016 by The source may be found in the GitHub repository `TomAugspurger/effective-pandas `_. -- `Modern Pandas `_ -- `Method Chaining `_ -- `Indexes `_ -- `Performance `_ -- `Tidy Data `_ -- `Visualization `_ -- `Timeseries `_ +* `Modern Pandas `_ +* `Method Chaining `_ +* `Indexes `_ +* `Performance `_ +* `Tidy Data `_ +* `Visualization `_ +* `Timeseries `_ Excel charts with pandas, vincent and xlsxwriter ------------------------------------------------ -- `Using Pandas and XlsxWriter to create Excel charts `_ +* `Using Pandas and XlsxWriter to create Excel charts `_ Video Tutorials --------------- -- `Pandas From The Ground Up `_ +* `Pandas From The Ground Up `_ (2015) (2:24) `GitHub repo `__ -- `Introduction Into Pandas `_ +* `Introduction Into Pandas `_ (2016) (1:28) `GitHub repo `__ -- `Pandas: .head() to .tail() `_ +* `Pandas: .head() to .tail() `_ (2016) (1:26) `GitHub repo `__ @@ -194,12 +197,11 @@ Video Tutorials Various Tutorials ----------------- -- `Wes McKinney's (pandas BDFL) blog `_ -- `Statistical analysis made easy in Python with SciPy and pandas DataFrames, by Randal Olson `_ -- `Statistical Data Analysis in Python, tutorial videos, by Christopher Fonnesbeck from SciPy 2013 `_ -- `Financial analysis in Python, by Thomas Wiecki `_ -- `Intro to pandas data structures, by Greg Reda `_ -- `Pandas and Python: Top 10, by Manish Amde `_ -- `Pandas Tutorial, by Mikhail Semeniuk `_ -- `Pandas DataFrames Tutorial, by Karlijn Willems `_ -- `A concise tutorial with real life examples `_ +* `Wes McKinney's (pandas BDFL) blog `_ +* `Statistical analysis made easy in Python with SciPy and pandas DataFrames, by Randal Olson `_ +* `Statistical Data Analysis in Python, tutorial videos, by Christopher Fonnesbeck from SciPy 2013 `_ +* `Financial analysis in Python, by Thomas Wiecki `_ +* `Intro to pandas data structures, by Greg Reda `_ +* `Pandas and Python: Top 10, by Manish Amde `_ +* `Pandas DataFrames Tutorial, by Karlijn Willems `_ +* `A concise tutorial with real life examples `_ diff --git a/doc/source/visualization.rst b/doc/source/visualization.rst index 09a52ee527cb5..569a6fb7b7a0d 100644 --- a/doc/source/visualization.rst +++ b/doc/source/visualization.rst @@ -1061,7 +1061,7 @@ Plot Formatting Setting the plot style ~~~~~~~~~~~~~~~~~~~~~~ -From version 1.5 and up, matplotlib offers a range of preconfigured plotting styles. Setting the +From version 1.5 and up, matplotlib offers a range of pre-configured plotting styles. Setting the style can be used to easily give plots the general look that you want. Setting the style is as easy as calling ``matplotlib.style.use(my_plot_style)`` before creating your plot. For example you could write ``matplotlib.style.use('ggplot')`` for ggplot-style @@ -1381,9 +1381,9 @@ Plotting with error bars is supported in :meth:`DataFrame.plot` and :meth:`Serie Horizontal and vertical error bars can be supplied to the ``xerr`` and ``yerr`` keyword arguments to :meth:`~DataFrame.plot()`. The error values can be specified using a variety of formats: -- As a :class:`DataFrame` or ``dict`` of errors with column names matching the ``columns`` attribute of the plotting :class:`DataFrame` or matching the ``name`` attribute of the :class:`Series`. -- As a ``str`` indicating which of the columns of plotting :class:`DataFrame` contain the error values. -- As raw values (``list``, ``tuple``, or ``np.ndarray``). Must be the same length as the plotting :class:`DataFrame`/:class:`Series`. +* As a :class:`DataFrame` or ``dict`` of errors with column names matching the ``columns`` attribute of the plotting :class:`DataFrame` or matching the ``name`` attribute of the :class:`Series`. +* As a ``str`` indicating which of the columns of plotting :class:`DataFrame` contain the error values. +* As raw values (``list``, ``tuple``, or ``np.ndarray``). Must be the same length as the plotting :class:`DataFrame`/:class:`Series`. Asymmetrical error bars are also supported, however raw error values must be provided in this case. For a ``M`` length :class:`Series`, a ``Mx2`` array should be provided indicating lower and upper (or left and right) errors. For a ``MxN`` :class:`DataFrame`, asymmetrical errors should be in a ``Mx2xN`` array. diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst index c744e44b4c17c..8672685b3ebb4 100644 --- a/doc/source/whatsnew.rst +++ b/doc/source/whatsnew.rst @@ -20,6 +20,12 @@ These are new features and improvements of note in each release. .. include:: whatsnew/v0.24.0.txt +.. include:: whatsnew/v0.23.4.txt + +.. include:: whatsnew/v0.23.3.txt + +.. include:: whatsnew/v0.23.2.txt + .. include:: whatsnew/v0.23.1.txt .. include:: whatsnew/v0.23.0.txt diff --git a/doc/source/whatsnew/v0.10.0.txt b/doc/source/whatsnew/v0.10.0.txt index 3fc05158b7fe7..298088a4f96b3 100644 --- a/doc/source/whatsnew/v0.10.0.txt +++ b/doc/source/whatsnew/v0.10.0.txt @@ -281,17 +281,12 @@ The old behavior of printing out summary information can be achieved via the The width of each line can be changed via 'line_width' (80 by default): -.. ipython:: python +.. code-block:: python pd.set_option('line_width', 40) wide_frame -.. ipython:: python - :suppress: - - pd.reset_option('line_width') - Updated PyTables Support ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -370,7 +365,7 @@ Updated PyTables Support df1.get_dtype_counts() - performance improvements on table writing -- support for arbitrarily indexed dimensions +- support for arbitrarly indexed dimensions - ``SparseSeries`` now has a ``density`` property (:issue:`2384`) - enable ``Series.str.strip/lstrip/rstrip`` methods to take an input argument to strip arbitrary characters (:issue:`2411`) diff --git a/doc/source/whatsnew/v0.10.1.txt b/doc/source/whatsnew/v0.10.1.txt index 2d5843101dec2..f1a32440c6950 100644 --- a/doc/source/whatsnew/v0.10.1.txt +++ b/doc/source/whatsnew/v0.10.1.txt @@ -93,7 +93,7 @@ columns, this is equivalent to passing a store.select('df',columns = ['A','B']) -``HDFStore`` now serializes multi-index dataframes when appending tables. +``HDFStore`` now serializes MultiIndex dataframes when appending tables. .. ipython:: python @@ -149,7 +149,7 @@ combined result, by using ``where`` on a selector table. `nan`. - You can pass ``index`` to ``append``. This defaults to ``True``. This will - automagically create indicies on the *indexables* and *data columns* of the + automagically create indices on the *indexables* and *data columns* of the table - You can pass ``chunksize=an integer`` to ``append``, to change the writing @@ -157,7 +157,7 @@ combined result, by using ``where`` on a selector table. on writing. - You can pass ``expectedrows=an integer`` to the first ``append``, to set the - TOTAL number of expectedrows that ``PyTables`` will expected. This will + TOTAL number of expected rows that ``PyTables`` will expected. This will optimize read/write performance. - ``Select`` now supports passing ``start`` and ``stop`` to provide selection @@ -191,7 +191,7 @@ combined result, by using ``where`` on a selector table. levels with a very large number of combinatorial values (:issue:`2684`) - Fixed bug that causes plotting to fail when the index is a DatetimeIndex with a fixed-offset timezone (:issue:`2683`) -- Corrected businessday subtraction logic when the offset is more than 5 bdays +- Corrected business day subtraction logic when the offset is more than 5 bdays and the starting date is on a weekend (:issue:`2680`) - Fixed C file parser behavior when the file has more columns than data (:issue:`2668`) diff --git a/doc/source/whatsnew/v0.11.0.txt b/doc/source/whatsnew/v0.11.0.txt index b90a597815ec5..f39e6c9ff459b 100644 --- a/doc/source/whatsnew/v0.11.0.txt +++ b/doc/source/whatsnew/v0.11.0.txt @@ -33,7 +33,7 @@ three types of multi-axis indexing. See more at :ref:`Selection by Label ` -- ``.iloc`` is strictly integer position based (from ``0`` to ``length-1`` of the axis), will raise ``IndexError`` when the requested indicies are out of bounds. Allowed inputs are: +- ``.iloc`` is strictly integer position based (from ``0`` to ``length-1`` of the axis), will raise ``IndexError`` when the requested indices are out of bounds. Allowed inputs are: - An integer e.g. ``5`` - A list or array of integers ``[4, 3, 0]`` @@ -44,7 +44,7 @@ three types of multi-axis indexing. - ``.ix`` supports mixed integer and label based access. It is primarily label based, but will fallback to integer positional access. ``.ix`` is the most general and will support any of the inputs to ``.loc`` and ``.iloc``, as well as support for floating point label schemes. ``.ix`` is especially useful when dealing with mixed positional and label - based hierarchial indexes. + based hierarchical indexes. As using integer slices with ``.ix`` have different behavior depending on whether the slice is interpreted as position based or label based, it's usually better to be @@ -76,7 +76,7 @@ Numeric dtypes will propagate and can coexist in DataFrames. If a dtype is passe df1.dtypes df2 = DataFrame(dict( A = Series(randn(8),dtype='float16'), B = Series(randn(8)), - C = Series(randn(8),dtype='uint8') )) + C = Series(range(8),dtype='uint8') )) df2 df2.dtypes @@ -211,7 +211,7 @@ Astype conversion on ``datetime64[ns]`` to ``object``, implicitly converts ``NaT API changes ~~~~~~~~~~~ - - Added to_series() method to indicies, to facilitate the creation of indexers + - Added to_series() method to indices, to facilitate the creation of indexers (:issue:`3275`) - ``HDFStore`` diff --git a/doc/source/whatsnew/v0.12.0.txt b/doc/source/whatsnew/v0.12.0.txt index ad33c49792d9f..f66f6c0f72d5d 100644 --- a/doc/source/whatsnew/v0.12.0.txt +++ b/doc/source/whatsnew/v0.12.0.txt @@ -7,7 +7,7 @@ This is a major release from 0.11.0 and includes several new features and enhancements along with a large number of bug fixes. Highlights include a consistent I/O API naming scheme, routines to read html, -write multi-indexes to csv files, read & write STATA data files, read & write JSON format +write MultiIndexes to csv files, read & write STATA data files, read & write JSON format files, Python 3 support for ``HDFStore``, filtering of groupby expressions via ``filter``, and a revamped ``replace`` routine that accepts regular expressions. @@ -73,7 +73,7 @@ API changes e.g. a boolean Series, even with integer labels, will raise. Since ``iloc`` is purely positional based, the labels on the Series are not alignable (:issue:`3631`) - This case is rarely used, and there are plently of alternatives. This preserves the + This case is rarely used, and there are plenty of alternatives. This preserves the ``iloc`` API to be *purely* positional based. .. ipython:: python @@ -166,7 +166,7 @@ API changes - The internal ``pandas`` class hierarchy has changed (slightly). The previous ``PandasObject`` now is called ``PandasContainer`` and a new - ``PandasObject`` has become the baseclass for ``PandasContainer`` as well + ``PandasObject`` has become the base class for ``PandasContainer`` as well as ``Index``, ``Categorical``, ``GroupBy``, ``SparseList``, and ``SparseArray`` (+ their base classes). Currently, ``PandasObject`` provides string methods (from ``StringMixin``). (:issue:`4090`, :issue:`4092`) @@ -296,7 +296,7 @@ Other Enhancements df.replace(regex=r'\s*\.\s*', value=np.nan) to replace all occurrences of the string ``'.'`` with zero or more - instances of surrounding whitespace with ``NaN``. + instances of surrounding white space with ``NaN``. Regular string replacement still works as expected. For example, you can do @@ -403,7 +403,7 @@ Bug Fixes :issue:`3572`, :issue:`3911`, :issue:`3912`), but they will try to convert object arrays to numeric arrays if possible so that you can still plot, for example, an object array with floats. This happens before any drawing takes place which - elimnates any spurious plots from showing up. + eliminates any spurious plots from showing up. - ``fillna`` methods now raise a ``TypeError`` if the ``value`` parameter is a list or tuple. diff --git a/doc/source/whatsnew/v0.13.0.txt b/doc/source/whatsnew/v0.13.0.txt index 02ddc362255ec..94cd451196ead 100644 --- a/doc/source/whatsnew/v0.13.0.txt +++ b/doc/source/whatsnew/v0.13.0.txt @@ -414,7 +414,7 @@ HDFStore API Changes - add the keyword ``dropna=True`` to ``append`` to change whether ALL nan rows are not written to the store (default is ``True``, ALL nan rows are NOT written), also settable via the option ``io.hdf.dropna_table`` (:issue:`4625`) -- pass thru store creation arguments; can be used to support in-memory stores +- pass through store creation arguments; can be used to support in-memory stores DataFrame repr Changes ~~~~~~~~~~~~~~~~~~~~~~ @@ -443,7 +443,7 @@ Enhancements - Clipboard functionality now works with PySide (:issue:`4282`) - Added a more informative error message when plot arguments contain overlapping color and style arguments (:issue:`4402`) -- ``to_dict`` now takes ``records`` as a possible outtype. Returns an array +- ``to_dict`` now takes ``records`` as a possible out type. Returns an array of column-keyed dictionaries. (:issue:`4936`) - ``NaN`` handing in get_dummies (:issue:`4446`) with `dummy_na` diff --git a/doc/source/whatsnew/v0.13.1.txt b/doc/source/whatsnew/v0.13.1.txt index 51ca6116d42ce..a4807a6d61b76 100644 --- a/doc/source/whatsnew/v0.13.1.txt +++ b/doc/source/whatsnew/v0.13.1.txt @@ -119,8 +119,7 @@ API changes equal. NaNs in identical locations are treated as equal. (:issue:`5283`) See also :ref:`the docs` for a motivating example. - .. ipython:: python - :okwarning: + .. code-block:: python df = DataFrame({'col':['foo', 0, np.nan]}) df2 = DataFrame({'col':[np.nan, 0, 'foo']}, index=[2,1,0]) diff --git a/doc/source/whatsnew/v0.14.0.txt b/doc/source/whatsnew/v0.14.0.txt index 92c699017fc13..d4b7b09c054d6 100644 --- a/doc/source/whatsnew/v0.14.0.txt +++ b/doc/source/whatsnew/v0.14.0.txt @@ -13,7 +13,7 @@ users upgrade to this version. - SQL interfaces updated to use ``sqlalchemy``, See :ref:`Here`. - Display interface changes, See :ref:`Here` - MultiIndexing Using Slicers, See :ref:`Here`. - - Ability to join a singly-indexed DataFrame with a multi-indexed DataFrame, see :ref:`Here ` + - Ability to join a singly-indexed DataFrame with a MultiIndexed DataFrame, see :ref:`Here ` - More consistency in groupby results and more flexible groupby specifications, See :ref:`Here` - Holiday calendars are now supported in ``CustomBusinessDay``, see :ref:`Here ` - Several improvements in plotting functions, including: hexbin, area and pie plots, see :ref:`Here`. @@ -78,10 +78,10 @@ API changes - ``df.iloc[len(df)::-1]`` now enumerates all elements in reverse - The :meth:`DataFrame.interpolate` keyword ``downcast`` default has been changed from ``infer`` to - ``None``. This is to preseve the original dtype unless explicitly requested otherwise (:issue:`6290`). + ``None``. This is to preserve the original dtype unless explicitly requested otherwise (:issue:`6290`). - When converting a dataframe to HTML it used to return `Empty DataFrame`. This special case has been removed, instead a header with the column names is returned (:issue:`6062`). -- ``Series`` and ``Index`` now internall share more common operations, e.g. ``factorize(),nunique(),value_counts()`` are +- ``Series`` and ``Index`` now internally share more common operations, e.g. ``factorize(),nunique(),value_counts()`` are now supported on ``Index`` types as well. The ``Series.weekday`` property from is removed from Series for API consistency. Using a ``DatetimeIndex/PeriodIndex`` method on a Series will now raise a ``TypeError``. (:issue:`4551`, :issue:`4056`, :issue:`5519`, :issue:`6380`, :issue:`7206`). @@ -294,7 +294,7 @@ Display Changes Text Parsing API Changes ~~~~~~~~~~~~~~~~~~~~~~~~ -:func:`read_csv`/:func:`read_table` will now be noiser w.r.t invalid options rather than falling back to the ``PythonParser``. +:func:`read_csv`/:func:`read_table` will now be noisier w.r.t invalid options rather than falling back to the ``PythonParser``. - Raise ``ValueError`` when ``sep`` specified with ``delim_whitespace=True`` in :func:`read_csv`/:func:`read_table` @@ -466,8 +466,8 @@ Some other enhancements to the sql functions include: MultiIndexing Using Slicers ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In 0.14.0 we added a new way to slice multi-indexed objects. -You can slice a multi-index by providing multiple indexers. +In 0.14.0 we added a new way to slice MultiIndexed objects. +You can slice a MultiIndex by providing multiple indexers. You can provide any of the selectors as if you are indexing by label, see :ref:`Selection by Label `, including slices, lists of labels, labels, and boolean indexers. @@ -519,7 +519,7 @@ See also issues (:issue:`6134`, :issue:`4036`, :issue:`3057`, :issue:`2598`, :is columns=columns).sort_index().sort_index(axis=1) df -Basic multi-index slicing using slices, lists, and labels. +Basic MultiIndex slicing using slices, lists, and labels. .. ipython:: python @@ -714,7 +714,7 @@ Deprecations Use the `percentiles` keyword instead, which takes a list of percentiles to display. The default output is unchanged. -- The default return type of :func:`boxplot` will change from a dict to a matpltolib Axes +- The default return type of :func:`boxplot` will change from a dict to a matplotlib Axes in a future release. You can use the future behavior now by passing ``return_type='axes'`` to boxplot. @@ -748,9 +748,9 @@ Enhancements - Add option to turn off escaping in ``DataFrame.to_latex`` (:issue:`6472`) - ``pd.read_clipboard`` will, if the keyword ``sep`` is unspecified, try to detect data copied from a spreadsheet and parse accordingly. (:issue:`6223`) -- Joining a singly-indexed DataFrame with a multi-indexed DataFrame (:issue:`3662`) +- Joining a singly-indexed DataFrame with a MultiIndexed DataFrame (:issue:`3662`) - See :ref:`the docs`. Joining multi-index DataFrames on both the left and right is not yet supported ATM. + See :ref:`the docs`. Joining MultiIndex DataFrames on both the left and right is not yet supported ATM. .. ipython:: python @@ -852,7 +852,7 @@ Performance - Performance improvement when converting ``DatetimeIndex`` to floating ordinals using ``DatetimeConverter`` (:issue:`6636`) - Performance improvement for ``DataFrame.shift`` (:issue:`5609`) -- Performance improvement in indexing into a multi-indexed Series (:issue:`5567`) +- Performance improvement in indexing into a MultiIndexed Series (:issue:`5567`) - Performance improvements in single-dtyped indexing (:issue:`6484`) - Improve performance of DataFrame construction with certain offsets, by removing faulty caching (e.g. MonthEnd,BusinessMonthEnd), (:issue:`6479`) @@ -881,7 +881,7 @@ Bug Fixes - Prevent segfault due to MultiIndex not being supported in HDFStore table format (:issue:`1848`) - Bug in ``pd.DataFrame.sort_index`` where mergesort wasn't stable when ``ascending=False`` (:issue:`6399`) -- Bug in ``pd.tseries.frequencies.to_offset`` when argument has leading zeroes (:issue:`6391`) +- Bug in ``pd.tseries.frequencies.to_offset`` when argument has leading zeros (:issue:`6391`) - Bug in version string gen. for dev versions with shallow clones / install from tarball (:issue:`6127`) - Inconsistent tz parsing ``Timestamp`` / ``to_datetime`` for current year (:issue:`5958`) - Indexing bugs with reordered indexes (:issue:`6252`, :issue:`6254`) @@ -896,7 +896,7 @@ Bug Fixes - Issue with groupby ``agg`` with a single function and a a mixed-type frame (:issue:`6337`) - Bug in ``DataFrame.replace()`` when passing a non- ``bool`` ``to_replace`` argument (:issue:`6332`) -- Raise when trying to align on different levels of a multi-index assignment (:issue:`3738`) +- Raise when trying to align on different levels of a MultiIndex assignment (:issue:`3738`) - Bug in setting complex dtypes via boolean indexing (:issue:`6345`) - Bug in TimeGrouper/resample when presented with a non-monotonic DatetimeIndex that would return invalid results. (:issue:`4161`) - Bug in index name propagation in TimeGrouper/resample (:issue:`4161`) @@ -922,7 +922,7 @@ Bug Fixes - Bug in ``Series.reindex`` when specifying a ``method`` with some nan values was inconsistent (noted on a resample) (:issue:`6418`) - Bug in :meth:`DataFrame.replace` where nested dicts were erroneously depending on the order of dictionary keys and values (:issue:`5338`). -- Perf issue in concatting with empty objects (:issue:`3259`) +- Performance issue in concatenating with empty objects (:issue:`3259`) - Clarify sorting of ``sym_diff`` on ``Index`` objects with ``NaN`` values (:issue:`6444`) - Regression in ``MultiIndex.from_product`` with a ``DatetimeIndex`` as input (:issue:`6439`) - Bug in ``str.extract`` when passed a non-default index (:issue:`6348`) @@ -966,8 +966,8 @@ Bug Fixes - Bug in downcasting inference with empty arrays (:issue:`6733`) - Bug in ``obj.blocks`` on sparse containers dropping all but the last items of same for dtype (:issue:`6748`) - Bug in unpickling ``NaT (NaTType)`` (:issue:`4606`) -- Bug in ``DataFrame.replace()`` where regex metacharacters were being treated - as regexs even when ``regex=False`` (:issue:`6777`). +- Bug in ``DataFrame.replace()`` where regex meta characters were being treated + as regex even when ``regex=False`` (:issue:`6777`). - Bug in timedelta ops on 32-bit platforms (:issue:`6808`) - Bug in setting a tz-aware index directly via ``.index`` (:issue:`6785`) - Bug in expressions.py where numexpr would try to evaluate arithmetic ops @@ -983,7 +983,7 @@ Bug Fixes would only replace the first occurrence of a value (:issue:`6689`) - Better error message when passing a frequency of 'MS' in ``Period`` construction (GH5332) - Bug in ``Series.__unicode__`` when ``max_rows=None`` and the Series has more than 1000 rows. (:issue:`6863`) -- Bug in ``groupby.get_group`` where a datetlike wasn't always accepted (:issue:`5267`) +- Bug in ``groupby.get_group`` where a datelike wasn't always accepted (:issue:`5267`) - Bug in ``groupBy.get_group`` created by ``TimeGrouper`` raises ``AttributeError`` (:issue:`6914`) - Bug in ``DatetimeIndex.tz_localize`` and ``DatetimeIndex.tz_convert`` converting ``NaT`` incorrectly (:issue:`5546`) - Bug in arithmetic operations affecting ``NaT`` (:issue:`6873`) @@ -994,9 +994,9 @@ Bug Fixes - Bug in ``DataFrame.plot`` and ``Series.plot``, where the legend behave inconsistently when plotting to the same axes repeatedly (:issue:`6678`) - Internal tests for patching ``__finalize__`` / bug in merge not finalizing (:issue:`6923`, :issue:`6927`) - accept ``TextFileReader`` in ``concat``, which was affecting a common user idiom (:issue:`6583`) -- Bug in C parser with leading whitespace (:issue:`3374`) +- Bug in C parser with leading white space (:issue:`3374`) - Bug in C parser with ``delim_whitespace=True`` and ``\r``-delimited lines -- Bug in python parser with explicit multi-index in row following column header (:issue:`6893`) +- Bug in python parser with explicit MultiIndex in row following column header (:issue:`6893`) - Bug in ``Series.rank`` and ``DataFrame.rank`` that caused small floats (<1e-13) to all receive the same rank (:issue:`6886`) - Bug in ``DataFrame.apply`` with functions that used ``*args`` or ``**kwargs`` and returned an empty result (:issue:`6952`) @@ -1043,7 +1043,7 @@ Bug Fixes - Bug in ``query``/``eval`` where global constants were not looked up correctly (:issue:`7178`) - Bug in recognizing out-of-bounds positional list indexers with ``iloc`` and a multi-axis tuple indexer (:issue:`7189`) -- Bug in setitem with a single value, multi-index and integer indices (:issue:`7190`, :issue:`7218`) +- Bug in setitem with a single value, MultiIndex and integer indices (:issue:`7190`, :issue:`7218`) - Bug in expressions evaluation with reversed ops, showing in series-dataframe ops (:issue:`7198`, :issue:`7192`) -- Bug in multi-axis indexing with > 2 ndim and a multi-index (:issue:`7199`) +- Bug in multi-axis indexing with > 2 ndim and a MultiIndex (:issue:`7199`) - Fix a bug where invalid eval/query operations would blow the stack (:issue:`5198`) diff --git a/doc/source/whatsnew/v0.14.1.txt b/doc/source/whatsnew/v0.14.1.txt index 32a2391c75531..d019cf54086c6 100644 --- a/doc/source/whatsnew/v0.14.1.txt +++ b/doc/source/whatsnew/v0.14.1.txt @@ -92,15 +92,15 @@ Enhancements ``offsets.apply``, ``rollforward`` and ``rollback`` resets the time (hour, minute, etc) or not (default ``False``, preserves time) (:issue:`7156`): - .. ipython:: python + .. code-block:: python - import pandas.tseries.offsets as offsets + import pandas.tseries.offsets as offsets - day = offsets.Day() - day.apply(Timestamp('2014-01-01 09:00')) + day = offsets.Day() + day.apply(Timestamp('2014-01-01 09:00')) - day = offsets.Day(normalize=True) - day.apply(Timestamp('2014-01-01 09:00')) + day = offsets.Day(normalize=True) + day.apply(Timestamp('2014-01-01 09:00')) - ``PeriodIndex`` is represented as the same format as ``DatetimeIndex`` (:issue:`7601`) - ``StringMethods`` now work on empty Series (:issue:`7242`) @@ -156,7 +156,7 @@ Experimental ~~~~~~~~~~~~ - ``pandas.io.data.Options`` has a new method, ``get_all_data`` method, and now consistently returns a - multi-indexed ``DataFrame`` (:issue:`5602`) + MultiIndexed ``DataFrame`` (:issue:`5602`) - ``io.gbq.read_gbq`` and ``io.gbq.to_gbq`` were refactored to remove the dependency on the Google ``bq.py`` command line client. This submodule now uses ``httplib2`` and the Google ``apiclient`` and ``oauth2client`` API client @@ -169,10 +169,10 @@ Experimental Bug Fixes ~~~~~~~~~ - Bug in ``DataFrame.where`` with a symmetric shaped frame and a passed other of a DataFrame (:issue:`7506`) -- Bug in Panel indexing with a multi-index axis (:issue:`7516`) +- Bug in Panel indexing with a MultiIndex axis (:issue:`7516`) - Regression in datetimelike slice indexing with a duplicated index and non-exact end-points (:issue:`7523`) - Bug in setitem with list-of-lists and single vs mixed types (:issue:`7551`:) -- Bug in timeops with non-aligned Series (:issue:`7500`) +- Bug in time ops with non-aligned Series (:issue:`7500`) - Bug in timedelta inference when assigning an incomplete Series (:issue:`7592`) - Bug in groupby ``.nth`` with a Series and integer-like column name (:issue:`7559`) - Bug in ``Series.get`` with a boolean accessor (:issue:`7407`) @@ -183,10 +183,10 @@ Bug Fixes - Bug in plotting subplots with ``DataFrame.plot``, ``hist`` clears passed ``ax`` even if the number of subplots is one (:issue:`7391`). - Bug in plotting subplots with ``DataFrame.boxplot`` with ``by`` kw raises ``ValueError`` if the number of subplots exceeds 1 (:issue:`7391`). - Bug in subplots displays ``ticklabels`` and ``labels`` in different rule (:issue:`5897`) -- Bug in ``Panel.apply`` with a multi-index as an axis (:issue:`7469`) +- Bug in ``Panel.apply`` with a MultiIndex as an axis (:issue:`7469`) - Bug in ``DatetimeIndex.insert`` doesn't preserve ``name`` and ``tz`` (:issue:`7299`) - Bug in ``DatetimeIndex.asobject`` doesn't preserve ``name`` (:issue:`7299`) -- Bug in multi-index slicing with datetimelike ranges (strings and Timestamps), (:issue:`7429`) +- Bug in MultiIndex slicing with datetimelike ranges (strings and Timestamps), (:issue:`7429`) - Bug in ``Index.min`` and ``max`` doesn't handle ``nan`` and ``NaT`` properly (:issue:`7261`) - Bug in ``PeriodIndex.min/max`` results in ``int`` (:issue:`7609`) - Bug in ``resample`` where ``fill_method`` was ignored if you passed ``how`` (:issue:`2073`) @@ -209,7 +209,7 @@ Bug Fixes - Bug in inferred_freq results in None for eastern hemisphere timezones (:issue:`7310`) - Bug in ``Easter`` returns incorrect date when offset is negative (:issue:`7195`) - Bug in broadcasting with ``.div``, integer dtypes and divide-by-zero (:issue:`7325`) -- Bug in ``CustomBusinessDay.apply`` raiases ``NameError`` when ``np.datetime64`` object is passed (:issue:`7196`) +- Bug in ``CustomBusinessDay.apply`` raises ``NameError`` when ``np.datetime64`` object is passed (:issue:`7196`) - Bug in ``MultiIndex.append``, ``concat`` and ``pivot_table`` don't preserve timezone (:issue:`6606`) - Bug in ``.loc`` with a list of indexers on a single-multi index level (that is not nested) (:issue:`7349`) - Bug in ``Series.map`` when mapping a dict with tuple keys of different lengths (:issue:`7333`) @@ -221,8 +221,8 @@ Bug Fixes - Bug where ``NDFrame.replace()`` didn't correctly replace objects with ``Period`` values (:issue:`7379`). - Bug in ``.ix`` getitem should always return a Series (:issue:`7150`) -- Bug in multi-index slicing with incomplete indexers (:issue:`7399`) -- Bug in multi-index slicing with a step in a sliced level (:issue:`7400`) +- Bug in MultiIndex slicing with incomplete indexers (:issue:`7399`) +- Bug in MultiIndex slicing with a step in a sliced level (:issue:`7400`) - Bug where negative indexers in ``DatetimeIndex`` were not correctly sliced (:issue:`7408`) - Bug where ``NaT`` wasn't repr'd correctly in a ``MultiIndex`` (:issue:`7406`, diff --git a/doc/source/whatsnew/v0.15.0.txt b/doc/source/whatsnew/v0.15.0.txt index 0f1a8c324de54..4be6975958af5 100644 --- a/doc/source/whatsnew/v0.15.0.txt +++ b/doc/source/whatsnew/v0.15.0.txt @@ -44,7 +44,7 @@ users upgrade to this version. .. warning:: - The refactorings in :class:`~pandas.Categorical` changed the two argument constructor from + The refactoring in :class:`~pandas.Categorical` changed the two argument constructor from "codes/labels and levels" to "values and levels (now called 'categories')". This can lead to subtle bugs. If you use :class:`~pandas.Categorical` directly, please audit your code before updating to this pandas version and change it to use the :meth:`~pandas.Categorical.from_codes` constructor. See more on ``Categorical`` :ref:`here ` @@ -139,7 +139,7 @@ This type is very similar to how ``Timestamp`` works for ``datetimes``. It is a The arguments to ``pd.to_timedelta`` are now ``(arg,unit='ns',box=True,coerce=False)``, previously were ``(arg,box=True,unit='ns')`` as these are more logical. -Consruct a scalar +Construct a scalar .. ipython:: python @@ -711,7 +711,7 @@ Other notable API changes: 2 7 NaN 3 11 NaN - Furthermore, ``.loc`` will raise If no values are found in a multi-index with a list-like indexer: + Furthermore, ``.loc`` will raise If no values are found in a MultiIndex with a list-like indexer: .. ipython:: python :okexcept: @@ -794,7 +794,7 @@ Other notable API changes: .. _whatsnew_0150.blanklines: - Made both the C-based and Python engines for `read_csv` and `read_table` ignore empty lines in input as well as - whitespace-filled lines, as long as ``sep`` is not whitespace. This is an API change + white space-filled lines, as long as ``sep`` is not white space. This is an API change that can be controlled by the keyword parameter ``skip_blank_lines``. See :ref:`the docs ` (:issue:`4466`) - A timeseries/index localized to UTC when inserted into a Series/DataFrame will preserve the UTC timezone @@ -940,7 +940,7 @@ Enhancements Enhancements in the importing/exporting of Stata files: -- Added support for bool, uint8, uint16 and uint32 datatypes in ``to_stata`` (:issue:`7097`, :issue:`7365`) +- Added support for bool, uint8, uint16 and uint32 data types in ``to_stata`` (:issue:`7097`, :issue:`7365`) - Added conversion option when importing Stata files (:issue:`8527`) - ``DataFrame.to_stata`` and ``StataWriter`` check string length for compatibility with limitations imposed in dta files where fixed-width @@ -988,7 +988,7 @@ Other: - Added ``split`` as an option to the ``orient`` argument in ``pd.DataFrame.to_dict``. (:issue:`7840`) - The ``get_dummies`` method can now be used on DataFrames. By default only - catagorical columns are encoded as 0's and 1's, while other columns are + categorical columns are encoded as 0's and 1's, while other columns are left untouched. .. ipython:: python @@ -1008,7 +1008,7 @@ Other: business_dates = date_range(start='4/1/2014', end='6/30/2014', freq='B') df = DataFrame(1, index=business_dates, columns=['a', 'b']) # get the first, 4th, and last date index for each month - df.groupby((df.index.year, df.index.month)).nth([0, 3, -1]) + df.groupby([df.index.year, df.index.month]).nth([0, 3, -1]) - ``Period`` and ``PeriodIndex`` supports addition/subtraction with ``timedelta``-likes (:issue:`7966`) @@ -1070,7 +1070,7 @@ Other: idx.duplicated() idx.drop_duplicates() -- add ``copy=True`` argument to ``pd.concat`` to enable pass thru of complete blocks (:issue:`8252`) +- add ``copy=True`` argument to ``pd.concat`` to enable pass through of complete blocks (:issue:`8252`) - Added support for numpy 1.8+ data types (``bool_``, ``int_``, ``float_``, ``string_``) for conversion to R dataframe (:issue:`8400`) @@ -1114,9 +1114,9 @@ Bug Fixes - Bug in ``DatetimeIndex`` and ``PeriodIndex`` in-place addition and subtraction cause different result from normal one (:issue:`6527`) - Bug in adding and subtracting ``PeriodIndex`` with ``PeriodIndex`` raise ``TypeError`` (:issue:`7741`) - Bug in ``combine_first`` with ``PeriodIndex`` data raises ``TypeError`` (:issue:`3367`) -- Bug in multi-index slicing with missing indexers (:issue:`7866`) -- Bug in multi-index slicing with various edge cases (:issue:`8132`) -- Regression in multi-index indexing with a non-scalar type object (:issue:`7914`) +- Bug in MultiIndex slicing with missing indexers (:issue:`7866`) +- Bug in MultiIndex slicing with various edge cases (:issue:`8132`) +- Regression in MultiIndex indexing with a non-scalar type object (:issue:`7914`) - Bug in ``Timestamp`` comparisons with ``==`` and ``int64`` dtype (:issue:`8058`) - Bug in pickles contains ``DateOffset`` may raise ``AttributeError`` when ``normalize`` attribute is referred internally (:issue:`7748`) - Bug in ``Panel`` when using ``major_xs`` and ``copy=False`` is passed (deprecation warning fails because of missing ``warnings``) (:issue:`8152`). @@ -1130,7 +1130,7 @@ Bug Fixes - Bug in ``get`` where an ``IndexError`` would not cause the default value to be returned (:issue:`7725`) - Bug in ``offsets.apply``, ``rollforward`` and ``rollback`` may reset nanosecond (:issue:`7697`) - Bug in ``offsets.apply``, ``rollforward`` and ``rollback`` may raise ``AttributeError`` if ``Timestamp`` has ``dateutil`` tzinfo (:issue:`7697`) -- Bug in sorting a multi-index frame with a ``Float64Index`` (:issue:`8017`) +- Bug in sorting a MultiIndex frame with a ``Float64Index`` (:issue:`8017`) - Bug in inconsistent panel setitem with a rhs of a ``DataFrame`` for alignment (:issue:`7763`) - Bug in ``is_superperiod`` and ``is_subperiod`` cannot handle higher frequencies than ``S`` (:issue:`7760`, :issue:`7772`, :issue:`7803`) - Bug in 32-bit platforms with ``Series.shift`` (:issue:`8129`) @@ -1212,7 +1212,7 @@ Bug Fixes - Bug in ``NDFrame.loc`` indexing when row/column names were lost when target was a list/ndarray (:issue:`6552`) - Regression in ``NDFrame.loc`` indexing when rows/columns were converted to Float64Index if target was an empty list/ndarray (:issue:`7774`) - Bug in ``Series`` that allows it to be indexed by a ``DataFrame`` which has unexpected results. Such indexing is no longer permitted (:issue:`8444`) -- Bug in item assignment of a ``DataFrame`` with multi-index columns where right-hand-side columns were not aligned (:issue:`7655`) +- Bug in item assignment of a ``DataFrame`` with MultiIndex columns where right-hand-side columns were not aligned (:issue:`7655`) - Suppress FutureWarning generated by NumPy when comparing object arrays containing NaN for equality (:issue:`7065`) - Bug in ``DataFrame.eval()`` where the dtype of the ``not`` operator (``~``) was not correctly inferred as ``bool``. diff --git a/doc/source/whatsnew/v0.15.1.txt b/doc/source/whatsnew/v0.15.1.txt index 345fc9f1b5da7..8cbf239ea20d0 100644 --- a/doc/source/whatsnew/v0.15.1.txt +++ b/doc/source/whatsnew/v0.15.1.txt @@ -72,7 +72,7 @@ API changes df.groupby(ts, as_index=False).max() -- ``groupby`` will not erroneously exclude columns if the column name conflics +- ``groupby`` will not erroneously exclude columns if the column name conflicts with the grouper name (:issue:`8112`): .. ipython:: python @@ -288,7 +288,7 @@ Bug Fixes - Bug in Panel indexing with a list-like (:issue:`8710`) - Compat issue is ``DataFrame.dtypes`` when ``options.mode.use_inf_as_null`` is True (:issue:`8722`) - Bug in ``read_csv``, ``dialect`` parameter would not take a string (:issue:`8703`) -- Bug in slicing a multi-index level with an empty-list (:issue:`8737`) +- Bug in slicing a MultiIndex level with an empty-list (:issue:`8737`) - Bug in numeric index operations of add/sub with Float/Index Index with numpy arrays (:issue:`8608`) - Bug in setitem with empty indexer and unwanted coercion of dtypes (:issue:`8669`) - Bug in ix/loc block splitting on setitem (manifests with integer-like dtypes, e.g. datetime64) (:issue:`8607`) diff --git a/doc/source/whatsnew/v0.15.2.txt b/doc/source/whatsnew/v0.15.2.txt index f1dfab0f57ed3..ee72fab7d23f2 100644 --- a/doc/source/whatsnew/v0.15.2.txt +++ b/doc/source/whatsnew/v0.15.2.txt @@ -165,7 +165,7 @@ Other enhancements: - Added support for ``utcfromtimestamp()``, ``fromtimestamp()``, and ``combine()`` on `Timestamp` class (:issue:`5351`). - Added Google Analytics (`pandas.io.ga`) basic documentation (:issue:`8835`). See `here `__. - ``Timedelta`` arithmetic returns ``NotImplemented`` in unknown cases, allowing extensions by custom classes (:issue:`8813`). -- ``Timedelta`` now supports arithemtic with ``numpy.ndarray`` objects of the appropriate dtype (numpy 1.8 or newer only) (:issue:`8884`). +- ``Timedelta`` now supports arithmetic with ``numpy.ndarray`` objects of the appropriate dtype (numpy 1.8 or newer only) (:issue:`8884`). - Added ``Timedelta.to_timedelta64()`` method to the public API (:issue:`8884`). - Added ``gbq.generate_bq_schema()`` function to the gbq module (:issue:`8325`). - ``Series`` now works with map objects the same way as generators (:issue:`8909`). @@ -173,7 +173,7 @@ Other enhancements: - ``to_datetime`` gains an ``exact`` keyword to allow for a format to not require an exact match for a provided format string (if its ``False``). ``exact`` defaults to ``True`` (meaning that exact matching is still the default) (:issue:`8904`) - Added ``axvlines`` boolean option to parallel_coordinates plot function, determines whether vertical lines will be printed, default is True - Added ability to read table footers to read_html (:issue:`8552`) -- ``to_sql`` now infers datatypes of non-NA values for columns that contain NA values and have dtype ``object`` (:issue:`8778`). +- ``to_sql`` now infers data types of non-NA values for columns that contain NA values and have dtype ``object`` (:issue:`8778`). .. _whatsnew_0152.performance: @@ -199,7 +199,7 @@ Bug Fixes - Bug in ``groupby`` signatures that didn't include \*args or \*\*kwargs (:issue:`8733`). - ``io.data.Options`` now raises ``RemoteDataError`` when no expiry dates are available from Yahoo and when it receives no data from Yahoo (:issue:`8761`), (:issue:`8783`). - Unclear error message in csv parsing when passing dtype and names and the parsed data is a different data type (:issue:`8833`) -- Bug in slicing a multi-index with an empty list and at least one boolean indexer (:issue:`8781`) +- Bug in slicing a MultiIndex with an empty list and at least one boolean indexer (:issue:`8781`) - ``io.data.Options`` now raises ``RemoteDataError`` when no expiry dates are available from Yahoo (:issue:`8761`). - ``Timedelta`` kwargs may now be numpy ints and floats (:issue:`8757`). - Fixed several outstanding bugs for ``Timedelta`` arithmetic and comparisons (:issue:`8813`, :issue:`5963`, :issue:`5436`). @@ -215,7 +215,7 @@ Bug Fixes - ``io.data.Options`` now raises ``RemoteDataError`` when no expiry dates are available from Yahoo and when it receives no data from Yahoo (:issue:`8761`), (:issue:`8783`). - Fix: The font size was only set on x axis if vertical or the y axis if horizontal. (:issue:`8765`) - Fixed division by 0 when reading big csv files in python 3 (:issue:`8621`) -- Bug in outputting a Multindex with ``to_html,index=False`` which would add an extra column (:issue:`8452`) +- Bug in outputting a MultiIndex with ``to_html,index=False`` which would add an extra column (:issue:`8452`) - Imported categorical variables from Stata files retain the ordinal information in the underlying data (:issue:`8836`). - Defined ``.size`` attribute across ``NDFrame`` objects to provide compat with numpy >= 1.9.1; buggy with ``np.array_split`` (:issue:`8846`) - Skip testing of histogram plots for matplotlib <= 1.2 (:issue:`8648`). @@ -230,11 +230,11 @@ Bug Fixes - Bug where index name was still used when plotting a series with ``use_index=False`` (:issue:`8558`). - Bugs when trying to stack multiple columns, when some (or all) of the level names are numbers (:issue:`8584`). - Bug in ``MultiIndex`` where ``__contains__`` returns wrong result if index is not lexically sorted or unique (:issue:`7724`) -- BUG CSV: fix problem with trailing whitespace in skipped rows, (:issue:`8679`), (:issue:`8661`), (:issue:`8983`) +- BUG CSV: fix problem with trailing white space in skipped rows, (:issue:`8679`), (:issue:`8661`), (:issue:`8983`) - Regression in ``Timestamp`` does not parse 'Z' zone designator for UTC (:issue:`8771`) - Bug in `StataWriter` the produces writes strings with 244 characters irrespective of actual size (:issue:`8969`) - Fixed ValueError raised by cummin/cummax when datetime64 Series contains NaT. (:issue:`8965`) -- Bug in Datareader returns object dtype if there are missing values (:issue:`8980`) +- Bug in DataReader returns object dtype if there are missing values (:issue:`8980`) - Bug in plotting if sharex was enabled and index was a timeseries, would show labels on multiple axes (:issue:`3964`). - Bug where passing a unit to the TimedeltaIndex constructor applied the to nano-second conversion twice. (:issue:`9011`). - Bug in plotting of a period-like array (:issue:`9012`) diff --git a/doc/source/whatsnew/v0.16.0.txt b/doc/source/whatsnew/v0.16.0.txt index 48af06d124f2e..ce525bbb4c1d6 100644 --- a/doc/source/whatsnew/v0.16.0.txt +++ b/doc/source/whatsnew/v0.16.0.txt @@ -133,7 +133,7 @@ from a ``scipy.sparse.coo_matrix``: String Methods Enhancements ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- Following new methods are accesible via ``.str`` accessor to apply the function to each values. This is intended to make it more consistent with standard methods on strings. (:issue:`9282`, :issue:`9352`, :issue:`9386`, :issue:`9387`, :issue:`9439`) +- Following new methods are accessible via ``.str`` accessor to apply the function to each values. This is intended to make it more consistent with standard methods on strings. (:issue:`9282`, :issue:`9352`, :issue:`9386`, :issue:`9387`, :issue:`9439`) ============= ============= ============= =============== =============== .. .. Methods .. .. @@ -530,7 +530,7 @@ Deprecations We refer users to the external package `pandas-qt `_. (:issue:`9615`) - The ``pandas.rpy`` interface is deprecated and will be removed in a future version. - Similar functionaility can be accessed thru the `rpy2 `_ project (:issue:`9602`) + Similar functionality can be accessed through the `rpy2 `_ project (:issue:`9602`) - Adding ``DatetimeIndex/PeriodIndex`` to another ``DatetimeIndex/PeriodIndex`` is being deprecated as a set-operation. This will be changed to a ``TypeError`` in a future version. ``.union()`` should be used for the union set operation. (:issue:`9094`) - Subtracting ``DatetimeIndex/PeriodIndex`` from another ``DatetimeIndex/PeriodIndex`` is being deprecated as a set-operation. This will be changed to an actual numeric subtraction yielding a ``TimeDeltaIndex`` in a future version. ``.difference()`` should be used for the differencing set operation. (:issue:`9094`) @@ -589,7 +589,7 @@ Bug Fixes - Fixed bug on big endian platforms which produced incorrect results in ``StataReader`` (:issue:`8688`). - Bug in ``MultiIndex.has_duplicates`` when having many levels causes an indexer overflow (:issue:`9075`, :issue:`5873`) - Bug in ``pivot`` and ``unstack`` where ``nan`` values would break index alignment (:issue:`4862`, :issue:`7401`, :issue:`7403`, :issue:`7405`, :issue:`7466`, :issue:`9497`) -- Bug in left ``join`` on multi-index with ``sort=True`` or null values (:issue:`9210`). +- Bug in left ``join`` on MultiIndex with ``sort=True`` or null values (:issue:`9210`). - Bug in ``MultiIndex`` where inserting new keys would fail (:issue:`9250`). - Bug in ``groupby`` when key space exceeds ``int64`` bounds (:issue:`9096`). - Bug in ``unstack`` with ``TimedeltaIndex`` or ``DatetimeIndex`` and nulls (:issue:`9491`). @@ -601,7 +601,7 @@ Bug Fixes - Bug in binary operator method (eg ``.mul()``) alignment with integer levels (:issue:`9463`). - Bug in boxplot, scatter and hexbin plot may show an unnecessary warning (:issue:`8877`) - Bug in subplot with ``layout`` kw may show unnecessary warning (:issue:`9464`) -- Bug in using grouper functions that need passed thru arguments (e.g. axis), when using wrapped function (e.g. ``fillna``), (:issue:`9221`) +- Bug in using grouper functions that need passed through arguments (e.g. axis), when using wrapped function (e.g. ``fillna``), (:issue:`9221`) - ``DataFrame`` now properly supports simultaneous ``copy`` and ``dtype`` arguments in constructor (:issue:`9099`) - Bug in ``read_csv`` when using skiprows on a file with CR line endings with the c engine. (:issue:`9079`) - ``isnull`` now detects ``NaT`` in ``PeriodIndex`` (:issue:`9129`) @@ -613,7 +613,7 @@ Bug Fixes - Fixed division by zero error for ``Series.kurt()`` when all values are equal (:issue:`9197`) - Fixed issue in the ``xlsxwriter`` engine where it added a default 'General' format to cells if no other format was applied. This prevented other row or column formatting being applied. (:issue:`9167`) - Fixes issue with ``index_col=False`` when ``usecols`` is also specified in ``read_csv``. (:issue:`9082`) -- Bug where ``wide_to_long`` would modify the input stubnames list (:issue:`9204`) +- Bug where ``wide_to_long`` would modify the input stub names list (:issue:`9204`) - Bug in ``to_sql`` not storing float64 values using double precision. (:issue:`9009`) - ``SparseSeries`` and ``SparsePanel`` now accept zero argument constructors (same as their non-sparse counterparts) (:issue:`9272`). - Regression in merging ``Categorical`` and ``object`` dtypes (:issue:`9426`) @@ -624,7 +624,7 @@ Bug Fixes - Fixed bug with reading CSV files from Amazon S3 on python 3 raising a TypeError (:issue:`9452`) - Bug in the Google BigQuery reader where the 'jobComplete' key may be present but False in the query results (:issue:`8728`) - Bug in ``Series.values_counts`` with excluding ``NaN`` for categorical type ``Series`` with ``dropna=True`` (:issue:`9443`) -- Fixed mising numeric_only option for ``DataFrame.std/var/sem`` (:issue:`9201`) +- Fixed missing numeric_only option for ``DataFrame.std/var/sem`` (:issue:`9201`) - Support constructing ``Panel`` or ``Panel4D`` with scalar data (:issue:`8285`) - ``Series`` text representation disconnected from `max_rows`/`max_columns` (:issue:`7508`). diff --git a/doc/source/whatsnew/v0.16.1.txt b/doc/source/whatsnew/v0.16.1.txt index 5c716f6ad45c1..d3a8064a0e786 100644 --- a/doc/source/whatsnew/v0.16.1.txt +++ b/doc/source/whatsnew/v0.16.1.txt @@ -237,7 +237,7 @@ enhancements make string operations easier and more consistent with standard pyt idx.str.startswith('a') s[s.index.str.startswith('a')] -- The following new methods are accesible via ``.str`` accessor to apply the function to each values. (:issue:`9766`, :issue:`9773`, :issue:`10031`, :issue:`10045`, :issue:`10052`) +- The following new methods are accessible via ``.str`` accessor to apply the function to each values. (:issue:`9766`, :issue:`9773`, :issue:`10031`, :issue:`10045`, :issue:`10052`) ================ =============== =============== =============== ================ .. .. Methods .. .. @@ -348,7 +348,7 @@ Deprecations Index Representation ~~~~~~~~~~~~~~~~~~~~ -The string representation of ``Index`` and its sub-classes have now been unified. These will show a single-line display if there are few values; a wrapped multi-line display for a lot of values (but less than ``display.max_seq_items``; if lots of items (> ``display.max_seq_items``) will show a truncated display (the head and tail of the data). The formatting for ``MultiIndex`` is unchanges (a multi-line wrapped display). The display width responds to the option ``display.max_seq_items``, which is defaulted to 100. (:issue:`6482`) +The string representation of ``Index`` and its sub-classes have now been unified. These will show a single-line display if there are few values; a wrapped multi-line display for a lot of values (but less than ``display.max_seq_items``; if lots of items (> ``display.max_seq_items``) will show a truncated display (the head and tail of the data). The formatting for ``MultiIndex`` is unchanged (a multi-line wrapped display). The display width responds to the option ``display.max_seq_items``, which is defaulted to 100. (:issue:`6482`) Previous Behavior @@ -437,8 +437,8 @@ Bug Fixes - Bug in ``to_msgpack`` and ``read_msgpack`` zlib and blosc compression support (:issue:`9783`) - Bug ``GroupBy.size`` doesn't attach index name properly if grouped by ``TimeGrouper`` (:issue:`9925`) - Bug causing an exception in slice assignments because ``length_of_indexer`` returns wrong results (:issue:`9995`) -- Bug in csv parser causing lines with initial whitespace plus one non-space character to be skipped. (:issue:`9710`) -- Bug in C csv parser causing spurious NaNs when data started with newline followed by whitespace. (:issue:`10022`) +- Bug in csv parser causing lines with initial white space plus one non-space character to be skipped. (:issue:`9710`) +- Bug in C csv parser causing spurious NaNs when data started with newline followed by white space. (:issue:`10022`) - Bug causing elements with a null group to spill into the final group when grouping by a ``Categorical`` (:issue:`9603`) - Bug where .iloc and .loc behavior is not consistent on empty dataframes (:issue:`9964`) - Bug in invalid attribute access on a ``TimedeltaIndex`` incorrectly raised ``ValueError`` instead of ``AttributeError`` (:issue:`9680`) @@ -455,7 +455,7 @@ Bug Fixes - Bug where using DataFrames asfreq would remove the name of the index. (:issue:`9885`) - Bug causing extra index point when resample BM/BQ (:issue:`9756`) - Changed caching in ``AbstractHolidayCalendar`` to be at the instance level rather than at the class level as the latter can result in unexpected behaviour. (:issue:`9552`) -- Fixed latex output for multi-indexed dataframes (:issue:`9778`) +- Fixed latex output for MultiIndexed dataframes (:issue:`9778`) - Bug causing an exception when setting an empty range using ``DataFrame.loc`` (:issue:`9596`) - Bug in hiding ticklabels with subplots and shared axes when adding a new plot to an existing grid of axes (:issue:`9158`) - Bug in ``transform`` and ``filter`` when grouping on a categorical variable (:issue:`9921`) diff --git a/doc/source/whatsnew/v0.16.2.txt b/doc/source/whatsnew/v0.16.2.txt index 29f6832b48aaf..047da4c94093b 100644 --- a/doc/source/whatsnew/v0.16.2.txt +++ b/doc/source/whatsnew/v0.16.2.txt @@ -125,7 +125,7 @@ Bug Fixes - Bug where ``HDFStore.select`` modifies the passed columns list (:issue:`7212`) - Bug in ``Categorical`` repr with ``display.width`` of ``None`` in Python 3 (:issue:`10087`) - Bug in ``to_json`` with certain orients and a ``CategoricalIndex`` would segfault (:issue:`10317`) -- Bug where some of the nan funcs do not have consistent return dtypes (:issue:`10251`) +- Bug where some of the nan functions do not have consistent return dtypes (:issue:`10251`) - Bug in ``DataFrame.quantile`` on checking that a valid axis was passed (:issue:`9543`) - Bug in ``groupby.apply`` aggregation for ``Categorical`` not preserving categories (:issue:`10138`) - Bug in ``to_csv`` where ``date_format`` is ignored if the ``datetime`` is fractional (:issue:`10209`) @@ -155,7 +155,7 @@ Bug Fixes - Bug in ``GroupBy.get_group`` raises ``ValueError`` when group key contains ``NaT`` (:issue:`6992`) - Bug in ``SparseSeries`` constructor ignores input data name (:issue:`10258`) - Bug in ``Categorical.remove_categories`` causing a ``ValueError`` when removing the ``NaN`` category if underlying dtype is floating-point (:issue:`10156`) -- Bug where infer_freq infers timerule (WOM-5XXX) unsupported by to_offset (:issue:`9425`) +- Bug where infer_freq infers time rule (WOM-5XXX) unsupported by to_offset (:issue:`9425`) - Bug in ``DataFrame.to_hdf()`` where table format would raise a seemingly unrelated error for invalid (non-string) column names. This is now explicitly forbidden. (:issue:`9057`) - Bug to handle masking empty ``DataFrame`` (:issue:`10126`). - Bug where MySQL interface could not handle numeric table/column names (:issue:`10255`) diff --git a/doc/source/whatsnew/v0.17.0.txt b/doc/source/whatsnew/v0.17.0.txt index ec8f318b72fef..404f2bf06e861 100644 --- a/doc/source/whatsnew/v0.17.0.txt +++ b/doc/source/whatsnew/v0.17.0.txt @@ -308,7 +308,7 @@ See the :ref:`documentation ` for more details. os.remove('test.xlsx') Previously, it was necessary to specify the ``has_index_names`` argument in ``read_excel``, -if the serialized data had index names. For version 0.17.0 the ouptput format of ``to_excel`` +if the serialized data had index names. For version 0.17.0 the output format of ``to_excel`` has been changed to make this keyword unnecessary - the change is shown below. **Old** @@ -1042,7 +1042,7 @@ Performance Improvements Bug Fixes ~~~~~~~~~ -- Bug in incorrection computation of ``.mean()`` on ``timedelta64[ns]`` because of overflow (:issue:`9442`) +- Bug in incorrect computation of ``.mean()`` on ``timedelta64[ns]`` because of overflow (:issue:`9442`) - Bug in ``.isin`` on older numpies (:issue:`11232`) - Bug in ``DataFrame.to_html(index=False)`` renders unnecessary ``name`` row (:issue:`10344`) - Bug in ``DataFrame.to_latex()`` the ``column_format`` argument could not be passed (:issue:`9402`) @@ -1069,7 +1069,7 @@ Bug Fixes - Bug in ``offsets.generate_range`` where ``start`` and ``end`` have finer precision than ``offset`` (:issue:`9907`) - Bug in ``pd.rolling_*`` where ``Series.name`` would be lost in the output (:issue:`10565`) - Bug in ``stack`` when index or columns are not unique. (:issue:`10417`) -- Bug in setting a ``Panel`` when an axis has a multi-index (:issue:`10360`) +- Bug in setting a ``Panel`` when an axis has a MultiIndex (:issue:`10360`) - Bug in ``USFederalHolidayCalendar`` where ``USMemorialDay`` and ``USMartinLutherKingJr`` were incorrect (:issue:`10278` and :issue:`9760` ) - Bug in ``.sample()`` where returned object, if set, gives unnecessary ``SettingWithCopyWarning`` (:issue:`10738`) - Bug in ``.sample()`` where weights passed as ``Series`` were not aligned along axis before being treated positionally, potentially causing problems if weight indices were not aligned with sampled object. (:issue:`10738`) diff --git a/doc/source/whatsnew/v0.17.1.txt b/doc/source/whatsnew/v0.17.1.txt index e1b561c4deacb..328a8193c8b13 100644 --- a/doc/source/whatsnew/v0.17.1.txt +++ b/doc/source/whatsnew/v0.17.1.txt @@ -41,7 +41,7 @@ Conditional HTML Formatting We've added *experimental* support for conditional HTML formatting: the visual styling of a DataFrame based on the data. The styling is accomplished with HTML and CSS. -Acesses the styler class with the :attr:`pandas.DataFrame.style`, attribute, +Accesses the styler class with the :attr:`pandas.DataFrame.style`, attribute, an instance of :class:`~pandas.core.style.Styler` with your data attached. Here's a quick example: @@ -58,7 +58,7 @@ We can render the HTML to get the following table. :file: whatsnew_0171_html_table.html :class:`~pandas.core.style.Styler` interacts nicely with the Jupyter Notebook. -See the :doc:`documentation