From 91dc84fe57545184ac1b069cc0c16497712ba5c1 Mon Sep 17 00:00:00 2001 From: TomAugspurger Date: Thu, 19 Sep 2013 23:00:47 -0500 Subject: [PATCH 01/26] BUG/TST: Fix failing FRED comparison tests. Fixes #4827 Assertions were being made about arrays (of one item) equaling a scaler. --- pandas/io/tests/test_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/tests/test_data.py b/pandas/io/tests/test_data.py index 1cffccea2289f..0e428ae4d0310 100644 --- a/pandas/io/tests/test_data.py +++ b/pandas/io/tests/test_data.py @@ -383,14 +383,14 @@ def test_fred_nan(self): start = datetime(2010, 1, 1) end = datetime(2013, 1, 27) df = web.DataReader("DFII5", "fred", start, end) - assert pd.isnull(df.ix['2010-01-01']) + assert pd.isnull(df.ix['2010-01-01'][0]) @network def test_fred_parts(self): start = datetime(2010, 1, 1) end = datetime(2013, 1, 27) df = web.get_data_fred("CPIAUCSL", start, end) - self.assertEqual(df.ix['2010-05-01'], 217.23) + self.assertEqual(df.ix['2010-05-01'][0], 217.23) t = df.CPIAUCSL.values assert np.issubdtype(t.dtype, np.floating) From 7b55b92975e9755b1c7a3883b8fd6d96ee088d0a Mon Sep 17 00:00:00 2001 From: jreback Date: Mon, 16 Sep 2013 15:37:41 -0400 Subject: [PATCH 02/26] DOC: v0.13.0.txt typos --- doc/source/release.rst | 2 ++ doc/source/v0.13.0.txt | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index f055bd0719527..f7755afe8caae 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -303,6 +303,8 @@ Experimental Features you to select elements of a ``DataFrame`` using a natural query syntax nearly identical to Python syntax. +.. _release.bug_fixes-0.13.0: + Bug Fixes ~~~~~~~~~ diff --git a/doc/source/v0.13.0.txt b/doc/source/v0.13.0.txt index 03ef7a5a8d783..4d7a3da8ace2b 100644 --- a/doc/source/v0.13.0.txt +++ b/doc/source/v0.13.0.txt @@ -444,7 +444,7 @@ Experimental .. ipython:: python n = 20 - df = DataFrame(randint(n, size=(n, 3)), columns=['a', 'b', 'c']) + df = DataFrame(np.random.randint(n, size=(n, 3)), columns=['a', 'b', 'c']) df.query('a < b < c') selects all the rows of ``df`` where ``a < b < c`` evaluates to ``True``. @@ -571,7 +571,7 @@ to unify methods and behaviors. Series formerly subclassed directly from Bug Fixes ~~~~~~~~~ -See :ref:`V0.13.0 Bug Fixes` for an extensive list of bugs that have been fixed in 0.13.0. +See :ref:`V0.13.0 Bug Fixes` for an extensive list of bugs that have been fixed in 0.13.0. See the :ref:`full release notes ` or issue tracker From 3652a5ebcdc8f4f51b3b166ce34d3d16976ac2db Mon Sep 17 00:00:00 2001 From: jreback Date: Mon, 16 Sep 2013 16:27:46 -0400 Subject: [PATCH 03/26] CLN: _axis_len checking cleanup and better message --- pandas/core/generic.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index b9ffe788d183d..2f6bc13983f93 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -244,7 +244,7 @@ def _get_axis_number(self, axis): return self._AXIS_NUMBERS[axis] except: pass - raise ValueError('No axis named %s' % axis) + raise ValueError('No axis named {0} for object type {1}'.format(axis,type(self))) def _get_axis_name(self, axis): axis = self._AXIS_ALIASES.get(axis, axis) @@ -256,7 +256,7 @@ def _get_axis_name(self, axis): return self._AXIS_NAMES[axis] except: pass - raise ValueError('No axis named %s' % axis) + raise ValueError('No axis named {0} for object type {1}'.format(axis,type(self))) def _get_axis(self, axis): name = self._get_axis_name(axis) @@ -496,7 +496,7 @@ def rename_axis(self, mapper, axis=0, copy=True, inplace=False): ------- renamed : type of caller """ - axis = self._AXIS_NAMES[axis] + axis = self._get_axis_name(axis) d = { 'copy' : copy, 'inplace' : inplace } d[axis] = mapper return self.rename(**d) @@ -1546,9 +1546,6 @@ def fillna(self, value=None, method=None, axis=0, inplace=False, self._consolidate_inplace() axis = self._get_axis_number(axis) - if axis + 1 > self._AXIS_LEN: - raise ValueError( - "invalid axis passed for object type {0}".format(type(self))) method = com._clean_fill_method(method) if value is None: From 41ee54c1b452af7dd498bcab1ec1aa2937d51449 Mon Sep 17 00:00:00 2001 From: jreback Date: Mon, 16 Sep 2013 16:49:17 -0400 Subject: [PATCH 04/26] COMPAT: unicode compat issue fix --- pandas/compat/__init__.py | 8 ++++++++ pandas/io/pytables.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pandas/compat/__init__.py b/pandas/compat/__init__.py index 10e1464739203..a2531ebd43c82 100644 --- a/pandas/compat/__init__.py +++ b/pandas/compat/__init__.py @@ -180,6 +180,8 @@ class to receive bound method def u(s): return s + def u_safe(s): + return s else: string_types = basestring, integer_types = (int, long) @@ -190,6 +192,12 @@ def u(s): def u(s): return unicode(s, "unicode_escape") + def u_safe(s): + try: + return unicode(s, "unicode_escape") + except: + return s + string_and_binary_types = string_types + (binary_type,) diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py index b79408a1bf8d2..5c1dd408f696c 100644 --- a/pandas/io/pytables.py +++ b/pandas/io/pytables.py @@ -29,7 +29,7 @@ import pandas.core.common as com from pandas.tools.merge import concat from pandas import compat -from pandas.compat import u, PY3, range, lrange +from pandas.compat import u_safe as u, PY3, range, lrange from pandas.io.common import PerformanceWarning from pandas.core.config import get_option from pandas.computation.pytables import Expr, maybe_expression From 4104c03215b0b239aa7f2da03fe04fb68fcc7319 Mon Sep 17 00:00:00 2001 From: jreback Date: Mon, 16 Sep 2013 21:01:34 -0400 Subject: [PATCH 05/26] TST: reproducing tests for subtle PyTables bug (disabled for now), see: https://github.com/PyTables/PyTables/issues/282 --- pandas/io/tests/test_pytables.py | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pandas/io/tests/test_pytables.py b/pandas/io/tests/test_pytables.py index 322b626acc0ad..ee438cb2cd45a 100644 --- a/pandas/io/tests/test_pytables.py +++ b/pandas/io/tests/test_pytables.py @@ -2695,6 +2695,46 @@ def test_select_dtypes(self): expected = df.reindex(index=list(df.index)[0:10],columns=['A']) tm.assert_frame_equal(expected, result) + with ensure_clean(self.path) as store: + + # floats w/o NaN + df = DataFrame(dict(cols = range(11), values = range(11)),dtype='float64') + df['cols'] = (df['cols']+10).apply(str) + + store.append('df1',df,data_columns=True) + result = store.select( + 'df1', where='values>2.0') + expected = df[df['values']>2.0] + tm.assert_frame_equal(expected, result) + + # floats with NaN + df.iloc[0] = np.nan + expected = df[df['values']>2.0] + + store.append('df2',df,data_columns=True,index=False) + result = store.select( + 'df2', where='values>2.0') + tm.assert_frame_equal(expected, result) + + # https://github.com/PyTables/PyTables/issues/282 + # bug in selection when 0th row has a np.nan and an index + #store.append('df3',df,data_columns=True) + #result = store.select( + # 'df3', where='values>2.0') + #tm.assert_frame_equal(expected, result) + + # not in first position float with NaN ok too + df = DataFrame(dict(cols = range(11), values = range(11)),dtype='float64') + df['cols'] = (df['cols']+10).apply(str) + + df.iloc[1] = np.nan + expected = df[df['values']>2.0] + + store.append('df4',df,data_columns=True) + result = store.select( + 'df4', where='values>2.0') + tm.assert_frame_equal(expected, result) + def test_select_with_many_inputs(self): with ensure_clean(self.path) as store: From cc302e2103702e8a535bc06d32594a6418e414a9 Mon Sep 17 00:00:00 2001 From: Phillip Cloud Date: Mon, 16 Sep 2013 21:04:48 -0400 Subject: [PATCH 06/26] DOC: show a more informative query performance plot Corrects the plot to show a larger range of numbers of rows to see where the Python and NumExpr lines intersect. --- bench/bench_with_subset.py | 2 +- doc/source/_static/query-perf-small.png | Bin 25662 -> 21731 bytes doc/source/indexing.rst | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/bench_with_subset.py b/bench/bench_with_subset.py index 99b98c9838a90..017401df3f7f3 100644 --- a/bench/bench_with_subset.py +++ b/bench/bench_with_subset.py @@ -112,5 +112,5 @@ def plot_perf(df, engines, title, filename=None): plot_perf(ev[ev.size <= 50000], engines, 'DataFrame.eval()', filename=join('eval-perf-small.png')) - plot_perf(qu[qu.size <= 100000], engines, 'DataFrame.query()', + plot_perf(qu[qu.size <= 500000], engines, 'DataFrame.query()', filename=join('query-perf-small.png')) diff --git a/doc/source/_static/query-perf-small.png b/doc/source/_static/query-perf-small.png index 56fcc787a66af981c839d2ae176f7a0c427f154a..e14fa69db7fe8b1cf177113e429c743253294fd8 100644 GIT binary patch literal 21731 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 Date: Mon, 16 Sep 2013 22:27:08 -0400 Subject: [PATCH 07/26] DOC: added bitwise op disallow clarification in eval docs --- doc/source/enhancingperf.rst | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/source/enhancingperf.rst b/doc/source/enhancingperf.rst index 87b68248c3e9e..bade382f03c59 100644 --- a/doc/source/enhancingperf.rst +++ b/doc/source/enhancingperf.rst @@ -386,11 +386,18 @@ Now let's do the same thing but with comparisons: .. note:: - Operations such as ``1 and 2`` should be performed in Python. An exception - will be raised if you try to performed any boolean or bitwise operations - with scalar operands that are not of type ``bool`` or ``np.bool_``. *This - includes bitwise operations on scalars.* You should perform these kinds of - operations in Python. + Operations such as + + .. code-block:: python + + 1 and 2 # would parse to 1 & 2, but should evaluate to 2 + 3 or 4 # would parse to 3 | 4, but should evaluate to 3 + ~1 # this is okay, but slower when using eval + + should be performed in Python. An exception will be raised if you try to + perform any boolean/bitwise operations with scalar operands that are not + of type ``bool`` or ``np.bool_``. Again, you should perform these kinds of + operations in plain Python. The ``DataFrame.eval`` method (Experimental) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From e69210452e238e5dd7c7eca23efefae49565429a Mon Sep 17 00:00:00 2001 From: Jeffrey Tratner Date: Fri, 13 Sep 2013 22:57:24 -0400 Subject: [PATCH 08/26] ENH: Better read_json error when handling bad keys (in json data with orient=split) --- doc/source/release.rst | 2 ++ pandas/io/json.py | 20 +++++++++++++++++--- pandas/io/tests/test_json/test_pandas.py | 4 ++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index f7755afe8caae..e4143e3f76a25 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -120,6 +120,8 @@ Improvements to existing features thanks @jnothman - ``__getitem__`` with ``tuple`` key (e.g., ``[:, 2]``) on ``Series`` without ``MultiIndex`` raises ``ValueError`` (:issue:`4759`, :issue:`4837`) + - ``read_json`` now raises a (more informative) ``ValueError`` when the dict + contains a bad key and ``orient='split'`` (:issue:`4730`, :issue:`4838`) API Changes ~~~~~~~~~~~ diff --git a/pandas/io/json.py b/pandas/io/json.py index 737ec1941b353..e3c85fae045d0 100644 --- a/pandas/io/json.py +++ b/pandas/io/json.py @@ -5,15 +5,14 @@ import pandas.json as _json from pandas.tslib import iNaT -from pandas.compat import long +from pandas.compat import long, u from pandas import compat, isnull from pandas import Series, DataFrame, to_datetime from pandas.io.common import get_filepath_or_buffer +import pandas.core.common as com loads = _json.loads dumps = _json.dumps - - ### interface to/from ### @@ -230,6 +229,14 @@ def __init__(self, json, orient, dtype=True, convert_axes=True, self.keep_default_dates = keep_default_dates self.obj = None + def check_keys_split(self, decoded): + "checks that dict has only the appropriate keys for orient='split'" + bad_keys = set(decoded.keys()).difference(set(self._split_keys)) + if bad_keys: + bad_keys = ", ".join(bad_keys) + raise ValueError(u("JSON data had unexpected key(s): %s") % + com.pprint_thing(bad_keys)) + def parse(self): # try numpy @@ -375,6 +382,8 @@ def _try_convert_dates(self): class SeriesParser(Parser): _default_orient = 'index' + _split_keys = ('name', 'index', 'data') + def _parse_no_numpy(self): @@ -385,6 +394,7 @@ def _parse_no_numpy(self): for k, v in compat.iteritems(loads( json, precise_float=self.precise_float))) + self.check_keys_split(decoded) self.obj = Series(dtype=None, **decoded) else: self.obj = Series( @@ -398,6 +408,7 @@ def _parse_numpy(self): decoded = loads(json, dtype=None, numpy=True, precise_float=self.precise_float) decoded = dict((str(k), v) for k, v in compat.iteritems(decoded)) + self.check_keys_split(decoded) self.obj = Series(**decoded) elif orient == "columns" or orient == "index": self.obj = Series(*loads(json, dtype=None, numpy=True, @@ -418,6 +429,7 @@ def _try_convert_types(self): class FrameParser(Parser): _default_orient = 'columns' + _split_keys = ('columns', 'index', 'data') def _parse_numpy(self): @@ -434,6 +446,7 @@ def _parse_numpy(self): decoded = loads(json, dtype=None, numpy=True, precise_float=self.precise_float) decoded = dict((str(k), v) for k, v in compat.iteritems(decoded)) + self.check_keys_split(decoded) self.obj = DataFrame(**decoded) elif orient == "values": self.obj = DataFrame(loads(json, dtype=None, numpy=True, @@ -456,6 +469,7 @@ def _parse_no_numpy(self): for k, v in compat.iteritems(loads( json, precise_float=self.precise_float))) + self.check_keys_split(decoded) self.obj = DataFrame(dtype=None, **decoded) elif orient == "index": self.obj = DataFrame( diff --git a/pandas/io/tests/test_json/test_pandas.py b/pandas/io/tests/test_json/test_pandas.py index 108e779129672..c32fc08dab297 100644 --- a/pandas/io/tests/test_json/test_pandas.py +++ b/pandas/io/tests/test_json/test_pandas.py @@ -252,8 +252,8 @@ def test_frame_from_json_bad_data(self): json = StringIO('{"badkey":["A","B"],' '"index":["2","3"],' '"data":[[1.0,"1"],[2.0,"2"],[null,"3"]]}') - self.assertRaises(TypeError, read_json, json, - orient="split") + with tm.assertRaisesRegexp(ValueError, r"unexpected key\(s\): badkey"): + read_json(json, orient="split") def test_frame_from_json_nones(self): df = DataFrame([[1, 2], [4, 5, 6]]) From df9e633a0ad96ce256a9ab11ca647bf9e196decd Mon Sep 17 00:00:00 2001 From: Jeffrey Tratner Date: Sun, 15 Sep 2013 12:50:11 -0400 Subject: [PATCH 09/26] BUG: fix sort_index with one col and ascending list now actually checks the first element of the list --- doc/source/release.rst | 4 ++++ pandas/core/frame.py | 7 ++++++- pandas/tests/test_frame.py | 19 ++++++++++++++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index e4143e3f76a25..793d52223b6f5 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -424,6 +424,10 @@ Bug Fixes across different versions of matplotlib (:issue:`4789`) - Suppressed DeprecationWarning associated with internal calls issued by repr() (:issue:`4391`) - Fixed an issue with a duplicate index and duplicate selector with ``.loc`` (:issue:`4825`) + - Fixed an issue with ``DataFrame.sort_index`` where, when sorting by a + single column and passing a list for ``ascending``, the argument for + ``ascending`` was being interpreted as ``True`` (:issue:`4839`, + :issue:`4846`) pandas 0.12.0 ------------- diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 75f81d20926a1..78ef806a45dcb 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -2856,7 +2856,7 @@ def sort_index(self, axis=0, by=None, ascending=True, inplace=False, Examples -------- - >>> result = df.sort_index(by=['A', 'B'], ascending=[1, 0]) + >>> result = df.sort_index(by=['A', 'B'], ascending=[True, False]) Returns ------- @@ -2875,6 +2875,9 @@ def sort_index(self, axis=0, by=None, ascending=True, inplace=False, raise ValueError('When sorting by column, axis must be 0 (rows)') if not isinstance(by, (tuple, list)): by = [by] + if com._is_sequence(ascending) and len(by) != len(ascending): + raise ValueError('Length of ascending (%d) != length of by' + ' (%d)' % (len(ascending), len(by))) if len(by) > 1: keys = [] @@ -2900,6 +2903,8 @@ def trans(v): raise ValueError('Cannot sort by duplicate column %s' % str(by)) indexer = k.argsort(kind=kind) + if isinstance(ascending, (tuple, list)): + ascending = ascending[0] if not ascending: indexer = indexer[::-1] elif isinstance(labels, MultiIndex): diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py index 752fc4c58ff3a..6f6b3bf71c759 100644 --- a/pandas/tests/test_frame.py +++ b/pandas/tests/test_frame.py @@ -8796,24 +8796,37 @@ def test_sort_index(self): expected = frame.ix[frame.index[indexer]] assert_frame_equal(sorted_df, expected) + sorted_df = frame.sort(columns='A', ascending=False) + assert_frame_equal(sorted_df, expected) + + # GH4839 + sorted_df = frame.sort(columns=['A'], ascending=[False]) + assert_frame_equal(sorted_df, expected) + # check for now sorted_df = frame.sort(columns='A') + assert_frame_equal(sorted_df, expected[::-1]) expected = frame.sort_index(by='A') assert_frame_equal(sorted_df, expected) - sorted_df = frame.sort(columns='A', ascending=False) - expected = frame.sort_index(by='A', ascending=False) - assert_frame_equal(sorted_df, expected) sorted_df = frame.sort(columns=['A', 'B'], ascending=False) expected = frame.sort_index(by=['A', 'B'], ascending=False) assert_frame_equal(sorted_df, expected) + sorted_df = frame.sort(columns=['A', 'B']) + assert_frame_equal(sorted_df, expected[::-1]) + self.assertRaises(ValueError, frame.sort_index, axis=2, inplace=True) + msg = 'When sorting by column, axis must be 0' with assertRaisesRegexp(ValueError, msg): frame.sort_index(by='A', axis=1) + msg = r'Length of ascending \(5\) != length of by \(2\)' + with assertRaisesRegexp(ValueError, msg): + frame.sort_index(by=['A', 'B'], axis=0, ascending=[True] * 5) + def test_sort_index_multicolumn(self): import random A = np.arange(5).repeat(20) From 48beb8e50bcd6ba533663f4a0d1444b99e21f6a7 Mon Sep 17 00:00:00 2001 From: jreback Date: Tue, 17 Sep 2013 07:35:46 -0400 Subject: [PATCH 10/26] DOC: v0.13.0 HDFStore corrections --- doc/source/v0.13.0.txt | 132 +++++++++++------------------------------ pandas/io/pytables.py | 4 +- 2 files changed, 36 insertions(+), 100 deletions(-) diff --git a/doc/source/v0.13.0.txt b/doc/source/v0.13.0.txt index 4d7a3da8ace2b..b46e2d4b5bc09 100644 --- a/doc/source/v0.13.0.txt +++ b/doc/source/v0.13.0.txt @@ -36,77 +36,6 @@ API changes an alias of iteritems used to get around ``2to3``'s changes). (:issue:`4384`, :issue:`4375`, :issue:`4372`) - ``Series.get`` with negative indexers now returns the same as ``[]`` (:issue:`4390`) - - ``HDFStore`` - - - Significant table writing performance improvements - - handle a passed ``Series`` in table format (:issue:`4330`) - - added an ``is_open`` property to indicate if the underlying file handle is_open; - a closed store will now report 'CLOSED' when viewing the store (rather than raising an error) - (:issue:`4409`) - - a close of a ``HDFStore`` now will close that instance of the ``HDFStore`` - but will only close the actual file if the ref count (by ``PyTables``) w.r.t. all of the open handles - are 0. Essentially you have a local instance of ``HDFStore`` referenced by a variable. Once you - close it, it will report closed. Other references (to the same file) will continue to operate - until they themselves are closed. Performing an action on a closed file will raise - ``ClosedFileError`` - - .. ipython:: python - - path = 'test.h5' - df = DataFrame(randn(10,2)) - store1 = HDFStore(path) - store2 = HDFStore(path) - store1.append('df',df) - store2.append('df2',df) - - store1 - store2 - store1.close() - store2 - store2.close() - store2 - - .. ipython:: python - :suppress: - - import os - os.remove(path) - - - removed the ``_quiet`` attribute, replace by a ``DuplicateWarning`` if retrieving - duplicate rows from a table (:issue:`4367`) - - removed the ``warn`` argument from ``open``. Instead a ``PossibleDataLossError`` exception will - be raised if you try to use ``mode='w'`` with an OPEN file handle (:issue:`4367`) - - ``select_as_coordinates`` will now return an ``Int64Index`` of the resultant selection set - See :ref:`here` for an example. - - allow a passed locations array or mask as a ``where`` condition (:issue:`4467`). - See :ref:`here` for an example. - - support ``timedelta64[ns]`` as a serialization type (:issue:`3577`). See :ref:`here` for an example. - - - the ``format`` keyword now replaces the ``table`` keyword; allowed values are ``fixed(f)`` or ``table(t)`` - the same defaults as prior < 0.13.0 remain, e.g. ``put`` implies 'fixed` or 'f' (Fixed) format - and ``append`` imples 'table' or 't' (Table) format - - .. ipython:: python - - path = 'test.h5' - df = DataFrame(randn(10,2)) - df.to_hdf(path,'df_table',format='table') - df.to_hdf(path,'df_table2',append=True) - df.to_hdf(path,'df_fixed') - with get_store(path) as store: - print store - - .. ipython:: python - :suppress: - - import os - os.remove(path) - - 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`) - - store `datetime.date` objects as ordinals rather then timetuples to avoid timezone issues (:issue:`2852`), - thanks @tavistmorph and @numpand - - Changes to how ``Index`` and ``MultiIndex`` handle metadata (``levels``, ``labels``, and ``names``) (:issue:`4039`): @@ -190,27 +119,55 @@ Indexing API Changes HDFStore API Changes ~~~~~~~~~~~~~~~~~~~~ - - Query Format Changes. A much more string-like query format is now supported. + - Query Format Changes. A much more string-like query format is now supported. See :ref:`the docs`. .. ipython:: python - path = 'test_query.h5' - dfq = DataFrame(randn(10,4),columns=list('ABCD'),index=date_range('20130101',periods=10)) + path = 'test.h5' + dfq = DataFrame(randn(10,4), + columns=list('ABCD'), + index=date_range('20130101',periods=10)) dfq.to_hdf(path,'dfq',format='table',data_columns=True) Use boolean expressions, with in-line function evaluation. .. ipython:: python - read_hdf(path,'dfq',where="index>Timestamp('20130104') & columns=['A', 'B']") + read_hdf(path,'dfq', + where="index>Timestamp('20130104') & columns=['A', 'B']") Use an inline column reference .. ipython:: python - read_hdf(path,'dfq',where="A>0 or C>0") + read_hdf(path,'dfq', + where="A>0 or C>0") - See :ref:`the docs`. + .. ipython:: python + :suppress: + + import os + os.remove(path) + + - the ``format`` keyword now replaces the ``table`` keyword; allowed values are ``fixed(f)`` or ``table(t)`` + the same defaults as prior < 0.13.0 remain, e.g. ``put`` implies ``fixed`` format + and ``append`` imples ``table`` format. This default format can be set as an option by setting ``io.hdf.default_format``. + + .. ipython:: python + + path = 'test.h5' + df = DataFrame(randn(10,2)) + df.to_hdf(path,'df_table',format='table') + df.to_hdf(path,'df_table2',append=True) + df.to_hdf(path,'df_fixed') + with get_store(path) as store: + print store + + .. ipython:: python + :suppress: + + import os + os.remove(path) - Significant table writing performance improvements - handle a passed ``Series`` in table format (:issue:`4330`) @@ -252,27 +209,6 @@ HDFStore API Changes be raised if you try to use ``mode='w'`` with an OPEN file handle (:issue:`4367`) - allow a passed locations array or mask as a ``where`` condition (:issue:`4467`). See :ref:`here` for an example. - - - the ``format`` keyword now replaces the ``table`` keyword; allowed values are ``fixed(f)`` or ``table(t)`` - the same defaults as prior < 0.13.0 remain, e.g. ``put`` implies 'fixed` or 'f' (Fixed) format - and ``append`` imples 'table' or 't' (Table) format - - .. ipython:: python - - path = 'test.h5' - df = DataFrame(randn(10,2)) - df.to_hdf(path,'df_table',format='table') - df.to_hdf(path,'df_table2',append=True) - df.to_hdf(path,'df_fixed') - with get_store(path) as store: - print store - - .. ipython:: python - :suppress: - - import os - os.remove('test.h5') - os.remove('test_query.h5') - 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`) diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py index 5c1dd408f696c..65edaed89c1f7 100644 --- a/pandas/io/pytables.py +++ b/pandas/io/pytables.py @@ -731,7 +731,7 @@ def put(self, key, value, format=None, append=False, **kwargs): allow more flexible operations like searching / selecting subsets of the data append : boolean, default False - For Table format, append the input data to the existing + This will force Table format, append the input data to the existing. encoding : default None, provide an encoding for strings """ if format is None: @@ -803,7 +803,7 @@ def append(self, key, value, format=None, append=True, columns=None, dropna=None Write as a PyTables Table structure which may perform worse but allow more flexible operations like searching / selecting subsets of the data - append : boolean, default True, append the input data to the existing + append : boolean, default True, append the input data to the existing data_columns : list of columns to create as data columns, or True to use all columns min_itemsize : dict of columns that specify minimum string sizes nan_rep : string to use as string nan represenation From 4c0fbca1d1f68c3983ef0716ca6ac224aa8642b3 Mon Sep 17 00:00:00 2001 From: jreback Date: Tue, 17 Sep 2013 08:01:03 -0400 Subject: [PATCH 11/26] BLD: fix json to build on windows, @Komnomnomnom --- pandas/src/ujson/python/objToJSON.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/src/ujson/python/objToJSON.c b/pandas/src/ujson/python/objToJSON.c index 715fbbcaa8856..aefddd7e47bcb 100644 --- a/pandas/src/ujson/python/objToJSON.c +++ b/pandas/src/ujson/python/objToJSON.c @@ -270,9 +270,9 @@ static void *PandasDateTimeStructToJSON(pandas_datetimestruct *dts, JSONTypeCont static void *NpyDateTimeToJSON(JSOBJ _obj, JSONTypeContext *tc, void *outValue, size_t *_outLen) { - PRINTMARK(); pandas_datetimestruct dts; PyDatetimeScalarObject *obj = (PyDatetimeScalarObject *) _obj; + PRINTMARK(); pandas_datetime_to_datetimestruct(obj->obval, obj->obmeta.base, &dts); return PandasDateTimeStructToJSON(&dts, tc, outValue, _outLen); From 2df7f83938c50350bea15297a18235301fe14f85 Mon Sep 17 00:00:00 2001 From: jreback Date: Tue, 17 Sep 2013 08:21:06 -0400 Subject: [PATCH 12/26] TST: dtypes tests fix for 32-bit --- pandas/tests/test_frame.py | 2 +- pandas/tests/test_indexing.py | 2 +- pandas/tests/test_series.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py index 6f6b3bf71c759..d216cebc1abf3 100644 --- a/pandas/tests/test_frame.py +++ b/pandas/tests/test_frame.py @@ -2743,7 +2743,7 @@ def test_constructor_generator(self): gen = ([ i, 'a'] for i in range(10)) result = DataFrame(gen) expected = DataFrame({ 0 : range(10), 1 : 'a' }) - assert_frame_equal(result, expected) + assert_frame_equal(result, expected, check_dtype=False) def test_constructor_list_of_dicts(self): data = [OrderedDict([['a', 1.5], ['b', 3], ['c', 4], ['d', 6]]), diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py index 0c862576b09a1..3453e69ed72b6 100644 --- a/pandas/tests/test_indexing.py +++ b/pandas/tests/test_indexing.py @@ -1362,7 +1362,7 @@ def f(): ### frame ### - df_orig = DataFrame(np.arange(6).reshape(3,2),columns=['A','B']) + df_orig = DataFrame(np.arange(6).reshape(3,2),columns=['A','B'],dtype='int64') # iloc/iat raise df = df_orig.copy() diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index c52fcad3d5111..686df18999850 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -355,8 +355,8 @@ def test_constructor_series(self): def test_constructor_iterator(self): - expected = Series(list(range(10))) - result = Series(range(10)) + expected = Series(list(range(10)),dtype='int64') + result = Series(range(10),dtype='int64') assert_series_equal(result, expected) def test_constructor_generator(self): From 8f0b4f5d0f6d3dde7e055f76e6960ab5005d345c Mon Sep 17 00:00:00 2001 From: Kieran O'Mahony Date: Sun, 15 Sep 2013 19:32:17 +1000 Subject: [PATCH 13/26] BLD: json mingw doesnt have sprintf_s --- pandas/src/ujson/lib/ultrajsonenc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/src/ujson/lib/ultrajsonenc.c b/pandas/src/ujson/lib/ultrajsonenc.c index 4106ed6b73fcf..15d92d42f6753 100644 --- a/pandas/src/ujson/lib/ultrajsonenc.c +++ b/pandas/src/ujson/lib/ultrajsonenc.c @@ -549,7 +549,7 @@ int Buffer_AppendDoubleUnchecked(JSOBJ obj, JSONObjectEncoder *enc, double value { precision_str[0] = '%'; precision_str[1] = '.'; -#ifdef _WIN32 +#if defined(_WIN32) && defined(_MSC_VER) sprintf_s(precision_str+2, sizeof(precision_str)-2, "%ug", enc->doublePrecision); enc->offset += sprintf_s(str, enc->end - enc->offset, precision_str, neg ? -value : value); #else From f3a539109291bf4f96cbf2611a765e5ce71b5a4a Mon Sep 17 00:00:00 2001 From: Kieran O'Mahony Date: Wed, 18 Sep 2013 15:56:22 +1000 Subject: [PATCH 14/26] TST: windows dtype 32-bit fixes --- pandas/io/tests/test_clipboard.py | 2 +- pandas/tests/test_graphics.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/tests/test_clipboard.py b/pandas/io/tests/test_clipboard.py index 12c696f7076a4..f5b5ba745d83c 100644 --- a/pandas/io/tests/test_clipboard.py +++ b/pandas/io/tests/test_clipboard.py @@ -43,7 +43,7 @@ def check_round_trip_frame(self, data_type): data = self.data[data_type] data.to_clipboard() result = read_clipboard() - tm.assert_frame_equal(data, result) + tm.assert_frame_equal(data, result, check_dtype=False) def test_round_trip_frame(self): for dt in self.data_types: diff --git a/pandas/tests/test_graphics.py b/pandas/tests/test_graphics.py index aa989e9d785f8..45289dac44254 100644 --- a/pandas/tests/test_graphics.py +++ b/pandas/tests/test_graphics.py @@ -492,7 +492,7 @@ def test_xcompat(self): def test_unsorted_index(self): df = DataFrame({'y': np.arange(100)}, - index=np.arange(99, -1, -1)) + index=np.arange(99, -1, -1), dtype=np.int64) ax = df.plot() l = ax.get_lines()[0] rs = l.get_xydata() From 7ebd3b150b2b2e902f6c6570d1eb891bbaba13c0 Mon Sep 17 00:00:00 2001 From: Kieran O'Mahony Date: Wed, 18 Sep 2013 06:46:22 +0000 Subject: [PATCH 15/26] FIX: iso date encoding year overflow --- pandas/src/datetime/np_datetime_strings.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/src/datetime/np_datetime_strings.c b/pandas/src/datetime/np_datetime_strings.c index 77ad8533f2831..9c78e995f4fe3 100644 --- a/pandas/src/datetime/np_datetime_strings.c +++ b/pandas/src/datetime/np_datetime_strings.c @@ -1148,7 +1148,7 @@ make_iso_8601_datetime(pandas_datetimestruct *dts, char *outstr, int outlen, #ifdef _WIN32 tmplen = _snprintf(substr, sublen, "%04" NPY_INT64_FMT, dts->year); #else - tmplen = snprintf(substr, sublen, "%04" NPY_INT64_FMT, (long int)dts->year); + tmplen = snprintf(substr, sublen, "%04" NPY_INT64_FMT, (long long)dts->year); #endif /* If it ran out of space or there isn't space for the NULL terminator */ if (tmplen < 0 || tmplen > sublen) { From 4a8f20e020cb82cb25e0df378c2a699338ad61be Mon Sep 17 00:00:00 2001 From: Dale Jung Date: Tue, 17 Sep 2013 03:55:40 -0400 Subject: [PATCH 16/26] ENH: Add makePeriodPanel --- pandas/util/testing.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 0718dc8926011..0481a522dabe8 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -460,12 +460,12 @@ def makeTimeDataFrame(nper=None): return DataFrame(data) -def getPeriodData(): - return dict((c, makePeriodSeries()) for c in getCols(K)) +def getPeriodData(nper=None): + return dict((c, makePeriodSeries(nper)) for c in getCols(K)) -def makePeriodFrame(): - data = getPeriodData() +def makePeriodFrame(nper=None): + data = getPeriodData(nper) return DataFrame(data) @@ -474,6 +474,10 @@ def makePanel(nper=None): data = dict((c, makeTimeDataFrame(nper)) for c in cols) return Panel.fromDict(data) +def makePeriodPanel(nper=None): + cols = ['Item' + c for c in string.ascii_uppercase[:K - 1]] + data = dict((c, makePeriodFrame(nper)) for c in cols) + return Panel.fromDict(data) def makePanel4D(nper=None): return Panel4D(dict(l1=makePanel(nper), l2=makePanel(nper), From f1fb8f0b616aaac627f47d99b76b255555912108 Mon Sep 17 00:00:00 2001 From: Dale Jung Date: Tue, 17 Sep 2013 05:05:38 -0400 Subject: [PATCH 17/26] BUG: (GH4853) Fixed Panel.shift/tshift to use freq. TST: Added Panel.tshift test case RST: Added to release notes CLN: on top of dale-jung 4863, remove shift from series.py, move to generic.py --- doc/source/release.rst | 1 + pandas/core/datetools.py | 20 ++++++++++ pandas/core/frame.py | 49 ----------------------- pandas/core/generic.py | 73 +++++++++++++++++++++++++++++++--- pandas/core/internals.py | 22 ++++++++--- pandas/core/panel.py | 8 +++- pandas/core/series.py | 78 ------------------------------------- pandas/sparse/series.py | 2 +- pandas/tests/test_panel.py | 38 ++++++++++++++++++ pandas/tests/test_series.py | 2 - 10 files changed, 151 insertions(+), 142 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index 793d52223b6f5..d747505593c94 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -428,6 +428,7 @@ Bug Fixes single column and passing a list for ``ascending``, the argument for ``ascending`` was being interpreted as ``True`` (:issue:`4839`, :issue:`4846`) + - Fixed ``Panel.tshift`` not working. Added `freq` support to ``Panel.shift`` (:issue:`4853`) pandas 0.12.0 ------------- diff --git a/pandas/core/datetools.py b/pandas/core/datetools.py index 228dc7574f8f3..91a29259d8f2f 100644 --- a/pandas/core/datetools.py +++ b/pandas/core/datetools.py @@ -35,3 +35,23 @@ isBusinessDay = BDay().onOffset isMonthEnd = MonthEnd().onOffset isBMonthEnd = BMonthEnd().onOffset + +def _resolve_offset(freq, kwds): + if 'timeRule' in kwds or 'offset' in kwds: + offset = kwds.get('offset', None) + offset = kwds.get('timeRule', offset) + if isinstance(offset, compat.string_types): + offset = getOffset(offset) + warn = True + else: + offset = freq + warn = False + + if warn: + import warnings + warnings.warn("'timeRule' and 'offset' parameters are deprecated," + " please use 'freq' instead", + FutureWarning) + + return offset + diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 78ef806a45dcb..70fcc2c9d9c0a 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -3497,55 +3497,6 @@ def diff(self, periods=1): new_data = self._data.diff(periods) return self._constructor(new_data) - def shift(self, periods=1, freq=None, **kwds): - """ - Shift the index of the DataFrame by desired number of periods with an - optional time freq - - Parameters - ---------- - periods : int - Number of periods to move, can be positive or negative - freq : DateOffset, timedelta, or time rule string, optional - Increment to use from datetools module or time rule (e.g. 'EOM') - - Notes - ----- - If freq is specified then the index values are shifted but the data - if not realigned - - Returns - ------- - shifted : DataFrame - """ - from pandas.core.series import _resolve_offset - - if periods == 0: - return self - - offset = _resolve_offset(freq, kwds) - - if isinstance(offset, compat.string_types): - offset = datetools.to_offset(offset) - - if offset is None: - indexer = com._shift_indexer(len(self), periods) - new_data = self._data.shift(indexer, periods) - elif isinstance(self.index, PeriodIndex): - orig_offset = datetools.to_offset(self.index.freq) - if offset == orig_offset: - new_data = self._data.copy() - new_data.axes[1] = self.index.shift(periods) - else: - msg = ('Given freq %s does not match PeriodIndex freq %s' % - (offset.rule_code, orig_offset.rule_code)) - raise ValueError(msg) - else: - new_data = self._data.copy() - new_data.axes[1] = self.index.shift(periods, offset) - - return self._constructor(new_data) - #---------------------------------------------------------------------- # Function application diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 2f6bc13983f93..53d3687854cac 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -11,8 +11,10 @@ import pandas.core.indexing as indexing from pandas.core.indexing import _maybe_convert_indices from pandas.tseries.index import DatetimeIndex +from pandas.tseries.period import PeriodIndex from pandas.core.internals import BlockManager import pandas.core.common as com +import pandas.core.datetools as datetools from pandas import compat, _np_version_under1p7 from pandas.compat import map, zip, lrange from pandas.core.common import (isnull, notnull, is_list_like, @@ -2667,7 +2669,40 @@ def cummin(self, axis=None, skipna=True): result = np.minimum.accumulate(y, axis) return self._wrap_array(result, self.axes, copy=False) - def tshift(self, periods=1, freq=None, **kwds): + def shift(self, periods=1, freq=None, axis=0, **kwds): + """ + Shift the index of the DataFrame by desired number of periods with an + optional time freq + + Parameters + ---------- + periods : int + Number of periods to move, can be positive or negative + freq : DateOffset, timedelta, or time rule string, optional + Increment to use from datetools module or time rule (e.g. 'EOM') + + Notes + ----- + If freq is specified then the index values are shifted but the data + if not realigned + + Returns + ------- + shifted : DataFrame + """ + if periods == 0: + return self + + if freq is None and not len(kwds): + block_axis = self._get_block_manager_axis(axis) + indexer = com._shift_indexer(len(self), periods) + new_data = self._data.shift(indexer, periods, axis=block_axis) + else: + return self.tshift(periods, freq, **kwds) + + return self._constructor(new_data) + + def tshift(self, periods=1, freq=None, axis=0, **kwds): """ Shift the time index, using the index's frequency if available @@ -2677,6 +2712,8 @@ def tshift(self, periods=1, freq=None, **kwds): Number of periods to move, can be positive or negative freq : DateOffset, timedelta, or time rule string, default None Increment to use from datetools module or time rule (e.g. 'EOM') + axis : int or basestring + Corresponds to the axis that contains the Index Notes ----- @@ -2686,19 +2723,45 @@ def tshift(self, periods=1, freq=None, **kwds): Returns ------- - shifted : Series + shifted : NDFrame """ + from pandas.core.datetools import _resolve_offset + + index = self._get_axis(axis) if freq is None: - freq = getattr(self.index, 'freq', None) + freq = getattr(index, 'freq', None) if freq is None: - freq = getattr(self.index, 'inferred_freq', None) + freq = getattr(index, 'inferred_freq', None) if freq is None: msg = 'Freq was not given and was not set in the index' raise ValueError(msg) - return self.shift(periods, freq, **kwds) + + if periods == 0: + return self + + offset = _resolve_offset(freq, kwds) + + if isinstance(offset, compat.string_types): + offset = datetools.to_offset(offset) + + block_axis = self._get_block_manager_axis(axis) + if isinstance(index, PeriodIndex): + orig_offset = datetools.to_offset(index.freq) + if offset == orig_offset: + new_data = self._data.copy() + new_data.axes[block_axis] = index.shift(periods) + else: + msg = ('Given freq %s does not match PeriodIndex freq %s' % + (offset.rule_code, orig_offset.rule_code)) + raise ValueError(msg) + else: + new_data = self._data.copy() + new_data.axes[block_axis] = index.shift(periods, offset) + + return self._constructor(new_data) def truncate(self, before=None, after=None, copy=True): """Function truncate a sorted DataFrame / Series before and/or after diff --git a/pandas/core/internals.py b/pandas/core/internals.py index 11ce27b078b18..4b9fdb0422526 100644 --- a/pandas/core/internals.py +++ b/pandas/core/internals.py @@ -758,17 +758,27 @@ def diff(self, n): new_values = com.diff(self.values, n, axis=1) return [make_block(new_values, self.items, self.ref_items, ndim=self.ndim, fastpath=True)] - def shift(self, indexer, periods): + def shift(self, indexer, periods, axis=0): """ shift the block by periods, possibly upcast """ - new_values = self.values.take(indexer, axis=1) + new_values = self.values.take(indexer, axis=axis) # convert integer to float if necessary. need to do a lot more than # that, handle boolean etc also new_values, fill_value = com._maybe_upcast(new_values) - if periods > 0: - new_values[:, :periods] = fill_value + + # 1-d + if self.ndim == 1: + if periods > 0: + new_values[:periods] = fill_value + else: + new_values[periods:] = fill_value + + # 2-d else: - new_values[:, periods:] = fill_value + if periods > 0: + new_values[:, :periods] = fill_value + else: + new_values[:, periods:] = fill_value return [make_block(new_values, self.items, self.ref_items, ndim=self.ndim, fastpath=True)] def eval(self, func, other, raise_on_error=True, try_cast=False): @@ -1547,7 +1557,7 @@ def fillna(self, value, inplace=False, downcast=None): values = self.values if inplace else self.values.copy() return [ self.make_block(values.get_values(value), fill_value=value) ] - def shift(self, indexer, periods): + def shift(self, indexer, periods, axis=0): """ shift the block by periods """ new_values = self.values.to_dense().take(indexer) diff --git a/pandas/core/panel.py b/pandas/core/panel.py index 6f02b49326e4d..45101b1e2afd5 100644 --- a/pandas/core/panel.py +++ b/pandas/core/panel.py @@ -1017,7 +1017,7 @@ def count(self, axis='major'): return self._wrap_result(result, axis) - def shift(self, lags, axis='major'): + def shift(self, lags, freq=None, axis='major'): """ Shift major or minor axis by specified number of leads/lags. Drops periods right now compared with DataFrame.shift @@ -1036,6 +1036,9 @@ def shift(self, lags, axis='major'): major_axis = self.major_axis minor_axis = self.minor_axis + if freq: + return self.tshift(lags, freq, axis=axis) + if lags > 0: vslicer = slice(None, -lags) islicer = slice(lags, None) @@ -1058,6 +1061,9 @@ def shift(self, lags, axis='major'): return self._constructor(values, items=items, major_axis=major_axis, minor_axis=minor_axis) + def tshift(self, periods=1, freq=None, axis='major', **kwds): + return super(Panel, self).tshift(periods, freq, axis, **kwds) + def truncate(self, before=None, after=None, axis='major'): """Function truncates a sorted Panel before and/or after some particular values on the requested axis diff --git a/pandas/core/series.py b/pandas/core/series.py index beb398dfe6fd0..9f7ab0cb0346b 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -59,8 +59,6 @@ _np_version_under1p6 = LooseVersion(_np_version) < '1.6' _np_version_under1p7 = LooseVersion(_np_version) < '1.7' -_SHOW_WARNINGS = True - class _TimeOp(object): """ Wrapper around Series datetime/time/timedelta arithmetic operations. @@ -2917,62 +2915,6 @@ def last_valid_index(self): #---------------------------------------------------------------------- # Time series-oriented methods - def shift(self, periods=1, freq=None, copy=True, **kwds): - """ - Shift the index of the Series by desired number of periods with an - optional time offset - - Parameters - ---------- - periods : int - Number of periods to move, can be positive or negative - freq : DateOffset, timedelta, or offset alias string, optional - Increment to use from datetools module or time rule (e.g. 'EOM') - - Returns - ------- - shifted : Series - """ - if periods == 0: - return self.copy() - - offset = _resolve_offset(freq, kwds) - - if isinstance(offset, compat.string_types): - offset = datetools.to_offset(offset) - - def _get_values(): - values = self.values - if copy: - values = values.copy() - return values - - if offset is None: - dtype, fill_value = _maybe_promote(self.dtype) - new_values = pa.empty(len(self), dtype=dtype) - - if periods > 0: - new_values[periods:] = self.values[:-periods] - new_values[:periods] = fill_value - elif periods < 0: - new_values[:periods] = self.values[-periods:] - new_values[periods:] = fill_value - - return self._constructor(new_values, index=self.index, name=self.name) - elif isinstance(self.index, PeriodIndex): - orig_offset = datetools.to_offset(self.index.freq) - if orig_offset == offset: - return self._constructor( - _get_values(), self.index.shift(periods), - name=self.name) - msg = ('Given freq %s does not match PeriodIndex freq %s' % - (offset.rule_code, orig_offset.rule_code)) - raise ValueError(msg) - else: - return self._constructor(_get_values(), - index=self.index.shift(periods, offset), - name=self.name) - def asof(self, where): """ Return last good (non-NaN) value in TimeSeries if value is NaN for @@ -3317,26 +3259,6 @@ def _try_cast(arr, take_fast_path): return subarr -def _resolve_offset(freq, kwds): - if 'timeRule' in kwds or 'offset' in kwds: - offset = kwds.get('offset', None) - offset = kwds.get('timeRule', offset) - if isinstance(offset, compat.string_types): - offset = datetools.getOffset(offset) - warn = True - else: - offset = freq - warn = False - - if warn and _SHOW_WARNINGS: # pragma: no cover - import warnings - warnings.warn("'timeRule' and 'offset' parameters are deprecated," - " please use 'freq' instead", - FutureWarning) - - return offset - - # backwards compatiblity TimeSeries = Series diff --git a/pandas/sparse/series.py b/pandas/sparse/series.py index 537b88db3c1f0..5cb29d717235d 100644 --- a/pandas/sparse/series.py +++ b/pandas/sparse/series.py @@ -605,7 +605,7 @@ def shift(self, periods, freq=None, **kwds): """ Analogous to Series.shift """ - from pandas.core.series import _resolve_offset + from pandas.core.datetools import _resolve_offset offset = _resolve_offset(freq, kwds) diff --git a/pandas/tests/test_panel.py b/pandas/tests/test_panel.py index fc86a78ea684b..a498cca528043 100644 --- a/pandas/tests/test_panel.py +++ b/pandas/tests/test_panel.py @@ -1323,6 +1323,44 @@ def test_shift(self): for i, f in compat.iteritems(self.panel))) assert_panel_equal(result, expected) + def test_tshift(self): + # PeriodIndex + ps = tm.makePeriodPanel() + shifted = ps.tshift(1) + unshifted = shifted.tshift(-1) + + assert_panel_equal(unshifted, ps) + + shifted2 = ps.tshift(freq='B') + assert_panel_equal(shifted, shifted2) + + shifted3 = ps.tshift(freq=bday) + assert_panel_equal(shifted, shifted3) + + assertRaisesRegexp(ValueError, 'does not match', ps.tshift, freq='M') + + # DatetimeIndex + panel = _panel + shifted = panel.tshift(1) + unshifted = shifted.tshift(-1) + + assert_panel_equal(panel, unshifted) + + shifted2 = panel.tshift(freq=panel.major_axis.freq) + assert_panel_equal(shifted, shifted2) + + inferred_ts = Panel(panel.values, + items=panel.items, + major_axis=Index(np.asarray(panel.major_axis)), + minor_axis=panel.minor_axis) + shifted = inferred_ts.tshift(1) + unshifted = shifted.tshift(-1) + assert_panel_equal(shifted, panel.tshift(1)) + assert_panel_equal(unshifted, inferred_ts) + + no_freq = panel.ix[:, [0, 5, 7], :] + self.assertRaises(ValueError, no_freq.tshift) + def test_multiindex_get(self): ind = MultiIndex.from_tuples([('a', 1), ('a', 2), ('b', 1), ('b', 2)], names=['first', 'second']) diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index 686df18999850..b142adbd5b949 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -3550,13 +3550,11 @@ def test_shift(self): self.assertRaises(ValueError, ps.shift, freq='D') # legacy support - smod._SHOW_WARNINGS = False shifted4 = ps.shift(1, timeRule='B') assert_series_equal(shifted2, shifted4) shifted5 = ps.shift(1, offset=datetools.bday) assert_series_equal(shifted5, shifted4) - smod._SHOW_WARNINGS = True def test_tshift(self): # PeriodIndex From 4af9288e2f23b5e5ef80544259f0db4a334100da Mon Sep 17 00:00:00 2001 From: jreback Date: Wed, 18 Sep 2013 09:32:29 -0400 Subject: [PATCH 18/26] DOC: v0.13.0 adds --- doc/source/v0.13.0.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/v0.13.0.txt b/doc/source/v0.13.0.txt index b46e2d4b5bc09..f30952b2b184c 100644 --- a/doc/source/v0.13.0.txt +++ b/doc/source/v0.13.0.txt @@ -171,6 +171,7 @@ HDFStore API Changes - Significant table writing performance improvements - handle a passed ``Series`` in table format (:issue:`4330`) + - can now serialize a ``timedelta64[ns]`` dtype in a table (:issue:`3577`), See :ref:`here for an example`. - added an ``is_open`` property to indicate if the underlying file handle is_open; a closed store will now report 'CLOSED' when viewing the store (rather than raising an error) (:issue:`4409`) From f33dfbb34cf81e8a6d10311a3bc9de577bd8c036 Mon Sep 17 00:00:00 2001 From: Phillip Cloud Date: Wed, 18 Sep 2013 13:23:20 -0400 Subject: [PATCH 19/26] CLN: clean up test_graphics.py --- pandas/tests/test_graphics.py | 245 +++++++++++++++++++--------------- 1 file changed, 140 insertions(+), 105 deletions(-) diff --git a/pandas/tests/test_graphics.py b/pandas/tests/test_graphics.py index 45289dac44254..cb6ec3d648afa 100644 --- a/pandas/tests/test_graphics.py +++ b/pandas/tests/test_graphics.py @@ -67,17 +67,16 @@ def test_plot(self): _check_plot_works(self.series[:5].plot, kind='line') _check_plot_works(self.series[:5].plot, kind='barh') _check_plot_works(self.series[:10].plot, kind='barh') - - Series(randn(10)).plot(kind='bar', color='black') + _check_plot_works(Series(randn(10)).plot, kind='bar', color='black') # figsize and title import matplotlib.pyplot as plt plt.close('all') ax = self.series.plot(title='Test', figsize=(16, 8)) - self.assert_(ax.title.get_text() == 'Test') - self.assert_((np.round(ax.figure.get_size_inches()) - == np.array((16., 8.))).all()) + self.assertEqual(ax.title.get_text(), 'Test') + assert_array_equal(np.round(ax.figure.get_size_inches()), + np.array((16., 8.))) @slow def test_bar_colors(self): @@ -97,7 +96,7 @@ def test_bar_colors(self): for i, rect in enumerate(rects[::5]): xp = conv.to_rgba(default_colors[i % len(default_colors)]) rs = rect.get_facecolor() - self.assert_(xp == rs) + self.assertEqual(xp, rs) plt.close('all') @@ -109,7 +108,7 @@ def test_bar_colors(self): for i, rect in enumerate(rects[::5]): xp = conv.to_rgba(custom_colors[i]) rs = rect.get_facecolor() - self.assert_(xp == rs) + self.assertEqual(xp, rs) plt.close('all') @@ -124,7 +123,7 @@ def test_bar_colors(self): for i, rect in enumerate(rects[::5]): xp = rgba_colors[i] rs = rect.get_facecolor() - self.assert_(xp == rs) + self.assertEqual(xp, rs) plt.close('all') @@ -137,7 +136,7 @@ def test_bar_colors(self): for i, rect in enumerate(rects[::5]): xp = rgba_colors[i] rs = rect.get_facecolor() - self.assert_(xp == rs) + self.assertEqual(xp, rs) plt.close('all') @@ -150,18 +149,18 @@ def test_bar_linewidth(self): # regular ax = df.plot(kind='bar', linewidth=2) for r in ax.patches: - self.assert_(r.get_linewidth() == 2) + self.assertEqual(r.get_linewidth(), 2) # stacked ax = df.plot(kind='bar', stacked=True, linewidth=2) for r in ax.patches: - self.assert_(r.get_linewidth() == 2) + self.assertEqual(r.get_linewidth(), 2) # subplots axes = df.plot(kind='bar', linewidth=2, subplots=True) for ax in axes: for r in ax.patches: - self.assert_(r.get_linewidth() == 2) + self.assertEqual(r.get_linewidth(), 2) @slow def test_bar_log(self): @@ -177,7 +176,7 @@ def test_rotation(self): df = DataFrame(randn(5, 5)) ax = df.plot(rot=30) for l in ax.get_xticklabels(): - self.assert_(l.get_rotation() == 30) + self.assertEqual(l.get_rotation(), 30) def test_irregular_datetime(self): rng = date_range('1/1/2000', '3/1/2000') @@ -186,7 +185,7 @@ def test_irregular_datetime(self): ax = ser.plot() xp = datetime(1999, 1, 1).toordinal() ax.set_xlim('1/1/1999', '1/1/2001') - self.assert_(xp == ax.get_xlim()[0]) + self.assertEqual(xp, ax.get_xlim()[0]) @slow def test_hist(self): @@ -205,8 +204,9 @@ def test_hist(self): fig, (ax1, ax2) = plt.subplots(1, 2) _check_plot_works(self.ts.hist, figure=fig, ax=ax1) _check_plot_works(self.ts.hist, figure=fig, ax=ax2) - self.assertRaises(ValueError, self.ts.hist, by=self.ts.index, - figure=fig) + + with tm.assertRaises(ValueError): + self.ts.hist(by=self.ts.index, figure=fig) @slow def test_hist_layout(self): @@ -216,8 +216,11 @@ def test_hist_layout(self): size=n)], 'height': random.normal(66, 4, size=n), 'weight': random.normal(161, 32, size=n)}) - self.assertRaises(ValueError, df.height.hist, layout=(1, 1)) - self.assertRaises(ValueError, df.height.hist, layout=[1, 1]) + with tm.assertRaises(ValueError): + df.height.hist(layout=(1, 1)) + + with tm.assertRaises(ValueError): + df.height.hist(layout=[1, 1]) @slow def test_hist_layout_with_by(self): @@ -231,10 +234,13 @@ def test_hist_layout_with_by(self): 'category': random.randint(4, size=n)}) _check_plot_works(df.height.hist, by=df.gender, layout=(2, 1)) plt.close('all') + _check_plot_works(df.height.hist, by=df.gender, layout=(1, 2)) plt.close('all') + _check_plot_works(df.weight.hist, by=df.category, layout=(1, 4)) plt.close('all') + _check_plot_works(df.weight.hist, by=df.category, layout=(4, 1)) plt.close('all') @@ -250,19 +256,20 @@ def test_hist_no_overlap(self): fig = gcf() axes = fig.get_axes() self.assertEqual(len(axes), 2) - close('all') @slow def test_plot_fails_with_dupe_color_and_style(self): x = Series(randn(2)) - self.assertRaises(ValueError, x.plot, style='k--', color='k') + with tm.assertRaises(ValueError): + x.plot(style='k--', color='k') def test_plot_fails_when_ax_differs_from_figure(self): - from pylab import figure + from pylab import figure, close fig1 = figure() fig2 = figure() ax1 = fig1.add_subplot(111) - self.assertRaises(AssertionError, self.ts.hist, ax=ax1, figure=fig2) + with tm.assertRaises(AssertionError): + self.ts.hist(ax=ax1, figure=fig2) @slow def test_kde(self): @@ -311,7 +318,8 @@ def test_invalid_plot_data(self): kinds = 'line', 'bar', 'barh', 'kde', 'density' for kind in kinds: - self.assertRaises(TypeError, s.plot, kind=kind) + with tm.assertRaises(TypeError): + s.plot(kind=kind) @slow def test_valid_object_plot(self): @@ -326,11 +334,13 @@ def test_partially_invalid_plot_data(self): kinds = 'line', 'bar', 'barh', 'kde', 'density' for kind in kinds: - self.assertRaises(TypeError, s.plot, kind=kind) + with tm.assertRaises(TypeError): + s.plot(kind=kind) def test_invalid_kind(self): s = Series([1, 2]) - self.assertRaises(ValueError, s.plot, kind='aasdf') + with tm.assertRaises(ValueError): + s.plot(kind='aasdf') @slow def test_dup_datetime_index_plot(self): @@ -342,7 +352,6 @@ def test_dup_datetime_index_plot(self): _check_plot_works(s.plot) - class TestDataFramePlots(unittest.TestCase): @classmethod @@ -406,23 +415,21 @@ def test_plot(self): def test_nonnumeric_exclude(self): import matplotlib.pyplot as plt - plt.close('all') - df = DataFrame({'A': ["x", "y", "z"], 'B': [1, 2, 3]}) ax = df.plot() - self.assert_(len(ax.get_lines()) == 1) # B was plotted + self.assertEqual(len(ax.get_lines()), 1) # B was plotted @slow - def test_label(self): - import matplotlib.pyplot as plt - plt.close('all') + def test_implicit_label(self): df = DataFrame(randn(10, 3), columns=['a', 'b', 'c']) ax = df.plot(x='a', y='b') - self.assert_(ax.xaxis.get_label().get_text() == 'a') + self.assertEqual(ax.xaxis.get_label().get_text(), 'a') - plt.close('all') + @slow + def test_explicit_label(self): + df = DataFrame(randn(10, 3), columns=['a', 'b', 'c']) ax = df.plot(x='a', y='b', label='LABEL') - self.assert_(ax.xaxis.get_label().get_text() == 'LABEL') + self.assertEqual(ax.xaxis.get_label().get_text(), 'LABEL') @slow def test_plot_xy(self): @@ -449,9 +456,9 @@ def test_plot_xy(self): plt.close('all') ax = df.plot(x=1, y=2, title='Test', figsize=(16, 8)) - self.assert_(ax.title.get_text() == 'Test') - self.assert_((np.round(ax.figure.get_size_inches()) - == np.array((16., 8.))).all()) + self.assertEqual(ax.title.get_text(), 'Test') + assert_array_equal(np.round(ax.figure.get_size_inches()), + np.array((16., 8.))) # columns.inferred_type == 'mixed' # TODO add MultiIndex test @@ -541,19 +548,25 @@ def test_subplots(self): @slow def test_plot_bar(self): + from matplotlib.pylab import close df = DataFrame(randn(6, 4), index=list(string.ascii_letters[:6]), columns=['one', 'two', 'three', 'four']) _check_plot_works(df.plot, kind='bar') + close('all') _check_plot_works(df.plot, kind='bar', legend=False) + close('all') _check_plot_works(df.plot, kind='bar', subplots=True) + close('all') _check_plot_works(df.plot, kind='bar', stacked=True) + close('all') df = DataFrame(randn(10, 15), index=list(string.ascii_letters[:10]), columns=lrange(15)) _check_plot_works(df.plot, kind='bar') + close('all') df = DataFrame({'a': [0, 1], 'b': [1, 0]}) _check_plot_works(df.plot, kind='bar') @@ -608,14 +621,11 @@ def test_boxplot(self): _check_plot_works(df.boxplot) _check_plot_works(df.boxplot, column=['one', 'two']) - _check_plot_works(df.boxplot, column=['one', 'two'], - by='indic') + _check_plot_works(df.boxplot, column=['one', 'two'], by='indic') _check_plot_works(df.boxplot, column='one', by=['indic', 'indic2']) _check_plot_works(df.boxplot, by='indic') _check_plot_works(df.boxplot, by=['indic', 'indic2']) - - _check_plot_works(lambda x: plotting.boxplot(x), df['one']) - + _check_plot_works(plotting.boxplot, df['one']) _check_plot_works(df.boxplot, notch=1) _check_plot_works(df.boxplot, by='indic', notch=1) @@ -633,7 +643,7 @@ def test_kde(self): self.assert_(ax.get_legend() is not None) axes = df.plot(kind='kde', logy=True, subplots=True) for ax in axes: - self.assert_(ax.get_yscale() == 'log') + self.assertEqual(ax.get_yscale(), 'log') @slow def test_hist(self): @@ -694,11 +704,13 @@ def test_hist(self): plt.close('all') ax = ser.hist(log=True) # scale of y must be 'log' - self.assert_(ax.get_yscale() == 'log') + self.assertEqual(ax.get_yscale(), 'log') plt.close('all') + # propagate attr exception from matplotlib.Axes.hist - self.assertRaises(AttributeError, ser.hist, foo='bar') + with tm.assertRaises(AttributeError): + ser.hist(foo='bar') @slow def test_hist_layout(self): @@ -716,14 +728,16 @@ def test_hist_layout(self): for layout_test in layout_to_expected_size: ax = df.hist(layout=layout_test['layout']) - self.assert_(len(ax) == layout_test['expected_size'][0]) - self.assert_(len(ax[0]) == layout_test['expected_size'][1]) + self.assertEqual(len(ax), layout_test['expected_size'][0]) + self.assertEqual(len(ax[0]), layout_test['expected_size'][1]) # layout too small for all 4 plots - self.assertRaises(ValueError, df.hist, layout=(1, 1)) + with tm.assertRaises(ValueError): + df.hist(layout=(1, 1)) # invalid format for layout - self.assertRaises(ValueError, df.hist, layout=(1,)) + with tm.assertRaises(ValueError): + df.hist(layout=(1,)) @slow def test_scatter(self): @@ -734,6 +748,7 @@ def test_scatter(self): def scat(**kwds): return plt.scatter_matrix(df, **kwds) + _check_plot_works(scat) _check_plot_works(scat, marker='+') _check_plot_works(scat, vmin=0) @@ -752,8 +767,10 @@ def scat2(x, y, by=None, ax=None, figsize=None): def test_andrews_curves(self): from pandas import read_csv from pandas.tools.plotting import andrews_curves - path = os.path.join(curpath(), 'data/iris.csv') + + path = os.path.join(curpath(), 'data', 'iris.csv') df = read_csv(path) + _check_plot_works(andrews_curves, df, 'Name') @slow @@ -761,7 +778,7 @@ def test_parallel_coordinates(self): from pandas import read_csv from pandas.tools.plotting import parallel_coordinates from matplotlib import cm - path = os.path.join(curpath(), 'data/iris.csv') + path = os.path.join(curpath(), 'data', 'iris.csv') df = read_csv(path) _check_plot_works(parallel_coordinates, df, 'Name') _check_plot_works(parallel_coordinates, df, 'Name', @@ -774,8 +791,8 @@ def test_parallel_coordinates(self): colors=['dodgerblue', 'aquamarine', 'seagreen']) _check_plot_works(parallel_coordinates, df, 'Name', colormap=cm.jet) - df = read_csv( - path, header=None, skiprows=1, names=[1, 2, 4, 8, 'Name']) + df = read_csv(path, header=None, skiprows=1, names=[1, 2, 4, 8, + 'Name']) _check_plot_works(parallel_coordinates, df, 'Name', use_columns=True) _check_plot_works(parallel_coordinates, df, 'Name', xticks=[1, 5, 25, 125]) @@ -785,7 +802,8 @@ def test_radviz(self): from pandas import read_csv from pandas.tools.plotting import radviz from matplotlib import cm - path = os.path.join(curpath(), 'data/iris.csv') + + path = os.path.join(curpath(), 'data', 'iris.csv') df = read_csv(path) _check_plot_works(radviz, df, 'Name') _check_plot_works(radviz, df, 'Name', colormap=cm.jet) @@ -803,10 +821,11 @@ def test_legend_name(self): ax = multi.plot() leg_title = ax.legend_.get_title() - self.assert_(leg_title.get_text(), 'group,individual') + self.assertEqual(leg_title.get_text(), 'group,individual') def _check_plot_fails(self, f, *args, **kwargs): - self.assertRaises(Exception, f, *args, **kwargs) + with tm.assertRaises(Exception): + f(*args, **kwargs) @slow def test_style_by_column(self): @@ -832,7 +851,6 @@ def test_line_colors(self): custom_colors = 'rgcby' - plt.close('all') df = DataFrame(randn(5, 5)) ax = df.plot(color=custom_colors) @@ -841,7 +859,7 @@ def test_line_colors(self): for i, l in enumerate(lines): xp = custom_colors[i] rs = l.get_color() - self.assert_(xp == rs) + self.assertEqual(xp, rs) tmp = sys.stderr sys.stderr = StringIO() @@ -850,7 +868,7 @@ def test_line_colors(self): ax2 = df.plot(colors=custom_colors) lines2 = ax2.get_lines() for l1, l2 in zip(lines, lines2): - self.assert_(l1.get_color(), l2.get_color()) + self.assertEqual(l1.get_color(), l2.get_color()) finally: sys.stderr = tmp @@ -864,7 +882,7 @@ def test_line_colors(self): for i, l in enumerate(lines): xp = rgba_colors[i] rs = l.get_color() - self.assert_(xp == rs) + self.assertEqual(xp, rs) plt.close('all') @@ -876,7 +894,7 @@ def test_line_colors(self): for i, l in enumerate(lines): xp = rgba_colors[i] rs = l.get_color() - self.assert_(xp == rs) + self.assertEqual(xp, rs) # make color a list if plotting one column frame # handles cases like df.plot(color='DodgerBlue') @@ -895,7 +913,7 @@ def test_default_color_cycle(self): for i, l in enumerate(lines): xp = plt.rcParams['axes.color_cycle'][i] rs = l.get_color() - self.assert_(xp == rs) + self.assertEqual(xp, rs) def test_unordered_ts(self): df = DataFrame(np.array([3.0, 2.0, 1.0]), @@ -907,13 +925,14 @@ def test_unordered_ts(self): xticks = ax.lines[0].get_xdata() self.assert_(xticks[0] < xticks[1]) ydata = ax.lines[0].get_ydata() - self.assert_(np.all(ydata == np.array([1.0, 2.0, 3.0]))) + assert_array_equal(ydata, np.array([1.0, 2.0, 3.0])) def test_all_invalid_plot_data(self): kinds = 'line', 'bar', 'barh', 'kde', 'density' df = DataFrame(list('abcd')) for kind in kinds: - self.assertRaises(TypeError, df.plot, kind=kind) + with tm.assertRaises(TypeError): + df.plot(kind=kind) @slow def test_partially_invalid_plot_data(self): @@ -921,11 +940,13 @@ def test_partially_invalid_plot_data(self): df = DataFrame(randn(10, 2), dtype=object) df[np.random.rand(df.shape[0]) > 0.5] = 'a' for kind in kinds: - self.assertRaises(TypeError, df.plot, kind=kind) + with tm.assertRaises(TypeError): + df.plot(kind=kind) def test_invalid_kind(self): df = DataFrame(randn(10, 2)) - self.assertRaises(ValueError, df.plot, kind='aasdf') + with tm.assertRaises(ValueError): + df.plot(kind='aasdf') class TestDataFrameGroupByPlots(unittest.TestCase): @@ -939,7 +960,8 @@ def setUpClass(cls): def tearDown(self): import matplotlib.pyplot as plt - plt.close('all') + for fignum in plt.get_fignums(): + plt.close(fignum) @slow def test_boxplot(self): @@ -955,36 +977,29 @@ def test_boxplot(self): grouped = df.groupby(level=1) _check_plot_works(grouped.boxplot) _check_plot_works(grouped.boxplot, subplots=False) + grouped = df.unstack(level=1).groupby(level=0, axis=1) _check_plot_works(grouped.boxplot) _check_plot_works(grouped.boxplot, subplots=False) def test_series_plot_color_kwargs(self): - # #1890 - import matplotlib.pyplot as plt - - plt.close('all') + # GH1890 ax = Series(np.arange(12) + 1).plot(color='green') line = ax.get_lines()[0] - self.assert_(line.get_color() == 'green') + self.assertEqual(line.get_color(), 'green') def test_time_series_plot_color_kwargs(self): # #1890 - import matplotlib.pyplot as plt - - plt.close('all') ax = Series(np.arange(12) + 1, index=date_range( '1/1/2000', periods=12)).plot(color='green') line = ax.get_lines()[0] - self.assert_(line.get_color() == 'green') + self.assertEqual(line.get_color(), 'green') def test_time_series_plot_color_with_empty_kwargs(self): import matplotlib as mpl - import matplotlib.pyplot as plt def_colors = mpl.rcParams['axes.color_cycle'] - plt.close('all') for i in range(3): ax = Series(np.arange(12) + 1, index=date_range('1/1/2000', periods=12)).plot() @@ -998,12 +1013,12 @@ def test_grouped_hist(self): df = DataFrame(randn(500, 2), columns=['A', 'B']) df['C'] = np.random.randint(0, 4, 500) axes = plotting.grouped_hist(df.A, by=df.C) - self.assert_(len(axes.ravel()) == 4) + self.assertEqual(len(axes.ravel()), 4) plt.close('all') axes = df.hist(by=df.C) - self.assert_(axes.ndim == 2) - self.assert_(len(axes.ravel()) == 4) + self.assertEqual(axes.ndim, 2) + self.assertEqual(len(axes.ravel()), 4) for ax in axes.ravel(): self.assert_(len(ax.patches) > 0) @@ -1022,12 +1037,13 @@ def test_grouped_hist(self): axes = plotting.grouped_hist(df.A, by=df.C, log=True) # scale of y must be 'log' for ax in axes.ravel(): - self.assert_(ax.get_yscale() == 'log') + self.assertEqual(ax.get_yscale(), 'log') plt.close('all') + # propagate attr exception from matplotlib.Axes.hist - self.assertRaises(AttributeError, plotting.grouped_hist, df.A, - by=df.C, foo='bar') + with tm.assertRaises(AttributeError): + plotting.grouped_hist(df.A, by=df.C, foo='bar') @slow def test_grouped_hist_layout(self): @@ -1057,49 +1073,67 @@ def test_grouped_hist_layout(self): layout=(4, 2)).shape, (4, 2)) @slow - def test_axis_shared(self): + def test_axis_share_x(self): # GH4089 - import matplotlib.pyplot as plt - def tick_text(tl): - return [x.get_text() for x in tl] - n = 100 - df = DataFrame({'gender': np.array(['Male', 'Female'])[random.randint(2, size=n)], + df = DataFrame({'gender': tm.choice(['Male', 'Female'], size=n), 'height': random.normal(66, 4, size=n), 'weight': random.normal(161, 32, size=n)}) ax1, ax2 = df.hist(column='height', by=df.gender, sharex=True) - self.assert_(ax1._shared_x_axes.joined(ax1, ax2)) + + # share x + self.assertTrue(ax1._shared_x_axes.joined(ax1, ax2)) + self.assertTrue(ax2._shared_x_axes.joined(ax1, ax2)) + + # don't share y self.assertFalse(ax1._shared_y_axes.joined(ax1, ax2)) - self.assert_(ax2._shared_x_axes.joined(ax1, ax2)) self.assertFalse(ax2._shared_y_axes.joined(ax1, ax2)) - plt.close('all') + @slow + def test_axis_share_y(self): + n = 100 + df = DataFrame({'gender': tm.choice(['Male', 'Female'], size=n), + 'height': random.normal(66, 4, size=n), + 'weight': random.normal(161, 32, size=n)}) ax1, ax2 = df.hist(column='height', by=df.gender, sharey=True) + + # share y + self.assertTrue(ax1._shared_y_axes.joined(ax1, ax2)) + self.assertTrue(ax2._shared_y_axes.joined(ax1, ax2)) + + # don't share x self.assertFalse(ax1._shared_x_axes.joined(ax1, ax2)) - self.assert_(ax1._shared_y_axes.joined(ax1, ax2)) self.assertFalse(ax2._shared_x_axes.joined(ax1, ax2)) - self.assert_(ax2._shared_y_axes.joined(ax1, ax2)) - plt.close('all') + @slow + def test_axis_share_xy(self): + n = 100 + df = DataFrame({'gender': tm.choice(['Male', 'Female'], size=n), + 'height': random.normal(66, 4, size=n), + 'weight': random.normal(161, 32, size=n)}) ax1, ax2 = df.hist(column='height', by=df.gender, sharex=True, sharey=True) - self.assert_(ax1._shared_x_axes.joined(ax1, ax2)) - self.assert_(ax1._shared_y_axes.joined(ax1, ax2)) - self.assert_(ax2._shared_x_axes.joined(ax1, ax2)) - self.assert_(ax2._shared_y_axes.joined(ax1, ax2)) + + # share both x and y + self.assertTrue(ax1._shared_x_axes.joined(ax1, ax2)) + self.assertTrue(ax2._shared_x_axes.joined(ax1, ax2)) + + self.assertTrue(ax1._shared_y_axes.joined(ax1, ax2)) + self.assertTrue(ax2._shared_y_axes.joined(ax1, ax2)) def test_option_mpl_style(self): set_option('display.mpl_style', 'default') set_option('display.mpl_style', None) set_option('display.mpl_style', False) - try: + + with tm.assertRaises(ValueError): set_option('display.mpl_style', 'default2') - except ValueError: - pass def test_invalid_colormap(self): df = DataFrame(randn(3, 2), columns=['A', 'B']) - self.assertRaises(ValueError, df.plot, colormap='invalid_colormap') + + with tm.assertRaises(ValueError): + df.plot(colormap='invalid_colormap') def assert_is_valid_plot_return_object(objs): @@ -1141,6 +1175,7 @@ def _check_plot_works(f, *args, **kwargs): with ensure_clean() as path: plt.savefig(path) + plt.close(fig) def curpath(): From 749669fc7bf7c848754c84e7ec4f02d311834964 Mon Sep 17 00:00:00 2001 From: Phillip Cloud Date: Wed, 18 Sep 2013 15:06:03 -0400 Subject: [PATCH 20/26] CLN: clean up tseries/tests/test_plotting.py --- pandas/tseries/tests/test_plotting.py | 349 +++++++++++--------------- 1 file changed, 151 insertions(+), 198 deletions(-) diff --git a/pandas/tseries/tests/test_plotting.py b/pandas/tseries/tests/test_plotting.py index 87cb65601bdd9..a22d2a65248a9 100644 --- a/pandas/tseries/tests/test_plotting.py +++ b/pandas/tseries/tests/test_plotting.py @@ -1,9 +1,8 @@ -import os from datetime import datetime, timedelta, date, time import unittest import nose -from pandas.compat import range, lrange, zip +from pandas.compat import lrange, zip import numpy as np from numpy.testing.decorators import slow @@ -52,48 +51,49 @@ def setUp(self): columns=['A', 'B', 'C']) for x in idx] + def tearDown(self): + import matplotlib.pyplot as plt + for fignum in plt.get_fignums(): + plt.close(fignum) + @slow def test_ts_plot_with_tz(self): # GH2877 - index = date_range('1/1/2011', periods=2, freq='H', tz='Europe/Brussels') + index = date_range('1/1/2011', periods=2, freq='H', + tz='Europe/Brussels') ts = Series([188.5, 328.25], index=index) - ts.plot() + _check_plot_works(ts.plot) @slow def test_frame_inferred(self): # inferred freq import matplotlib.pyplot as plt - plt.close('all') idx = date_range('1/1/1987', freq='MS', periods=100) idx = DatetimeIndex(idx.values, freq=None) + df = DataFrame(np.random.randn(len(idx), 3), index=idx) - df.plot() + _check_plot_works(df.plot) # axes freq idx = idx[0:40] + idx[45:99] df2 = DataFrame(np.random.randn(len(idx), 3), index=idx) - df2.plot() - plt.close('all') + _check_plot_works(df2.plot) # N > 1 idx = date_range('2008-1-1 00:15:00', freq='15T', periods=10) idx = DatetimeIndex(idx.values, freq=None) df = DataFrame(np.random.randn(len(idx), 3), index=idx) - df.plot() + _check_plot_works(df.plot) - @slow def test_nonnumeric_exclude(self): import matplotlib.pyplot as plt - plt.close('all') idx = date_range('1/1/1987', freq='A', periods=3) df = DataFrame({'A': ["x", "y", "z"], 'B': [1,2,3]}, idx) - plt.close('all') ax = df.plot() # it works self.assert_(len(ax.get_lines()) == 1) #B was plotted - - plt.close('all') + plt.close(plt.gcf()) self.assertRaises(TypeError, df['A'].plot) @@ -101,30 +101,23 @@ def test_nonnumeric_exclude(self): def test_tsplot(self): from pandas.tseries.plotting import tsplot import matplotlib.pyplot as plt - plt.close('all') ax = plt.gca() ts = tm.makeTimeSeries() - tsplot(ts, plt.Axes.plot) f = lambda *args, **kwds: tsplot(s, plt.Axes.plot, *args, **kwds) - plt.close('all') for s in self.period_ser: _check_plot_works(f, s.index.freq, ax=ax, series=s) - plt.close('all') + for s in self.datetime_ser: _check_plot_works(f, s.index.freq.rule_code, ax=ax, series=s) - plt.close('all') - plt.close('all') ax = ts.plot(style='k') - self.assert_((0., 0., 0.) == ax.get_lines()[0].get_color()) + self.assertEqual((0., 0., 0.), ax.get_lines()[0].get_color()) - @slow def test_both_style_and_color(self): import matplotlib.pyplot as plt - plt.close('all') ts = tm.makeTimeSeries() self.assertRaises(ValueError, ts.plot, style='b-', color='#000099') @@ -143,11 +136,11 @@ def test_high_freq(self): def test_get_datevalue(self): from pandas.tseries.converter import get_datevalue self.assert_(get_datevalue(None, 'D') is None) - self.assert_(get_datevalue(1987, 'A') == 1987) - self.assert_(get_datevalue(Period(1987, 'A'), 'M') == - Period('1987-12', 'M').ordinal) - self.assert_(get_datevalue('1/1/1987', 'D') == - Period('1987-1-1', 'D').ordinal) + self.assertEqual(get_datevalue(1987, 'A'), 1987) + self.assertEqual(get_datevalue(Period(1987, 'A'), 'M'), + Period('1987-12', 'M').ordinal) + self.assertEqual(get_datevalue('1/1/1987', 'D'), + Period('1987-1-1', 'D').ordinal) @slow def test_line_plot_period_series(self): @@ -179,7 +172,6 @@ def test_line_plot_inferred_freq(self): ser = ser[[0, 3, 5, 6]] _check_plot_works(ser.plot) - @slow def test_fake_inferred_business(self): import matplotlib.pyplot as plt fig = plt.gcf() @@ -189,7 +181,7 @@ def test_fake_inferred_business(self): ts = Series(lrange(len(rng)), rng) ts = ts[:3].append(ts[5:]) ax = ts.plot() - self.assert_(not hasattr(ax, 'freq')) + self.assertFalse(hasattr(ax, 'freq')) @slow def test_plot_offset_freq(self): @@ -227,8 +219,8 @@ def test_uhf(self): for loc, label in zip(tlocs, tlabels): xp = conv._from_ordinal(loc).strftime('%H:%M:%S.%f') rs = str(label.get_text()) - if len(rs) != 0: - self.assert_(xp == rs) + if len(rs): + self.assertEqual(xp, rs) @slow def test_irreg_hf(self): @@ -255,7 +247,6 @@ def test_irreg_hf(self): diffs = Series(ax.get_lines()[0].get_xydata()[:, 0]).diff() self.assert_((np.fabs(diffs[1:] - sec) < 1e-8).all()) - @slow def test_irregular_datetime64_repr_bug(self): import matplotlib.pyplot as plt ser = tm.makeTimeSeries() @@ -265,56 +256,52 @@ def test_irregular_datetime64_repr_bug(self): plt.clf() ax = fig.add_subplot(211) ret = ser.plot() - assert(ret is not None) + self.assert_(ret is not None) for rs, xp in zip(ax.get_lines()[0].get_xdata(), ser.index): - assert(rs == xp) + self.assertEqual(rs, xp) - @slow def test_business_freq(self): import matplotlib.pyplot as plt - plt.close('all') bts = tm.makePeriodSeries() ax = bts.plot() - self.assert_(ax.get_lines()[0].get_xydata()[0, 0], - bts.index[0].ordinal) + self.assertEqual(ax.get_lines()[0].get_xydata()[0, 0], + bts.index[0].ordinal) idx = ax.get_lines()[0].get_xdata() - self.assert_(PeriodIndex(data=idx).freqstr == 'B') + self.assertEqual(PeriodIndex(data=idx).freqstr, 'B') @slow def test_business_freq_convert(self): - import matplotlib.pyplot as plt - plt.close('all') n = tm.N tm.N = 300 bts = tm.makeTimeSeries().asfreq('BM') tm.N = n ts = bts.to_period('M') ax = bts.plot() - self.assert_(ax.get_lines()[0].get_xydata()[0, 0], ts.index[0].ordinal) + self.assertEqual(ax.get_lines()[0].get_xydata()[0, 0], + ts.index[0].ordinal) idx = ax.get_lines()[0].get_xdata() - self.assert_(PeriodIndex(data=idx).freqstr == 'M') + self.assertEqual(PeriodIndex(data=idx).freqstr, 'M') - @slow def test_nonzero_base(self): - import matplotlib.pyplot as plt - plt.close('all') - #GH2571 + # GH2571 idx = (date_range('2012-12-20', periods=24, freq='H') + timedelta(minutes=30)) df = DataFrame(np.arange(24), index=idx) ax = df.plot() rs = ax.get_lines()[0].get_xdata() - self.assert_(not Index(rs).is_normalized) + self.assertFalse(Index(rs).is_normalized) - @slow def test_dataframe(self): bts = DataFrame({'a': tm.makeTimeSeries()}) ax = bts.plot() idx = ax.get_lines()[0].get_xdata() + assert_array_equal(bts.index.to_period(), idx) @slow def test_axis_limits(self): + import matplotlib.pyplot as plt + def _test(ax): xlim = ax.get_xlim() ax.set_xlim(xlim[0] - 5, xlim[1] + 10) @@ -340,9 +327,7 @@ def _test(ax): result = ax.get_xlim() self.assertEqual(int(result[0]), expected[0].ordinal) self.assertEqual(int(result[1]), expected[1].ordinal) - - import matplotlib.pyplot as plt - plt.close('all') + plt.close(ax.get_figure()) ser = tm.makeTimeSeries() ax = ser.plot() @@ -354,7 +339,9 @@ def _test(ax): df = DataFrame({'a': ser, 'b': ser + 1}) axes = df.plot(subplots=True) - [_test(ax) for ax in axes] + + for ax in axes: + _test(ax) def test_get_finder(self): import pandas.tseries.converter as conv @@ -368,6 +355,7 @@ def test_get_finder(self): @slow def test_finder_daily(self): + import matplotlib.pyplot as plt xp = Period('1999-1-1', freq='B').ordinal day_lst = [10, 40, 252, 400, 950, 2750, 10000] for n in day_lst: @@ -377,35 +365,35 @@ def test_finder_daily(self): xaxis = ax.get_xaxis() rs = xaxis.get_majorticklocs()[0] self.assertEqual(xp, rs) - (vmin, vmax) = ax.get_xlim() + vmin, vmax = ax.get_xlim() ax.set_xlim(vmin + 0.9, vmax) rs = xaxis.get_majorticklocs()[0] self.assertEqual(xp, rs) + plt.close(ax.get_figure()) @slow def test_finder_quarterly(self): import matplotlib.pyplot as plt xp = Period('1988Q1').ordinal yrs = [3.5, 11] - plt.close('all') for n in yrs: rng = period_range('1987Q2', periods=int(n * 4), freq='Q') ser = Series(np.random.randn(len(rng)), rng) ax = ser.plot() xaxis = ax.get_xaxis() rs = xaxis.get_majorticklocs()[0] - self.assert_(rs == xp) + self.assertEqual(rs, xp) (vmin, vmax) = ax.get_xlim() ax.set_xlim(vmin + 0.9, vmax) rs = xaxis.get_majorticklocs()[0] self.assertEqual(xp, rs) + plt.close(ax.get_figure()) @slow def test_finder_monthly(self): import matplotlib.pyplot as plt xp = Period('Jan 1988').ordinal yrs = [1.15, 2.5, 4, 11] - plt.close('all') for n in yrs: rng = period_range('1987Q2', periods=int(n * 12), freq='M') ser = Series(np.random.randn(len(rng)), rng) @@ -413,28 +401,24 @@ def test_finder_monthly(self): xaxis = ax.get_xaxis() rs = xaxis.get_majorticklocs()[0] self.assert_(rs == xp) - (vmin, vmax) = ax.get_xlim() + vmin, vmax = ax.get_xlim() ax.set_xlim(vmin + 0.9, vmax) rs = xaxis.get_majorticklocs()[0] self.assertEqual(xp, rs) - plt.close('all') + plt.close(ax.get_figure()) - @slow def test_finder_monthly_long(self): - import matplotlib.pyplot as plt - plt.close('all') rng = period_range('1988Q1', periods=24 * 12, freq='M') ser = Series(np.random.randn(len(rng)), rng) ax = ser.plot() xaxis = ax.get_xaxis() rs = xaxis.get_majorticklocs()[0] xp = Period('1989Q1', 'M').ordinal - self.assert_(rs == xp) + self.assertEqual(rs, xp) @slow def test_finder_annual(self): import matplotlib.pyplot as plt - plt.close('all') xp = [1987, 1988, 1990, 1990, 1995, 2020, 2070, 2170] for i, nyears in enumerate([5, 10, 19, 49, 99, 199, 599, 1001]): rng = period_range('1987', periods=nyears, freq='A') @@ -442,13 +426,11 @@ def test_finder_annual(self): ax = ser.plot() xaxis = ax.get_xaxis() rs = xaxis.get_majorticklocs()[0] - self.assert_(rs == Period(xp[i], freq='A').ordinal) - plt.close('all') + self.assertEqual(rs, Period(xp[i], freq='A').ordinal) + plt.close(ax.get_figure()) @slow def test_finder_minutely(self): - import matplotlib.pyplot as plt - plt.close('all') nminutes = 50 * 24 * 60 rng = date_range('1/1/1999', freq='Min', periods=nminutes) ser = Series(np.random.randn(len(rng)), rng) @@ -458,10 +440,7 @@ def test_finder_minutely(self): xp = Period('1/1/1999', freq='Min').ordinal self.assertEqual(rs, xp) - @slow def test_finder_hourly(self): - import matplotlib.pyplot as plt - plt.close('all') nhours = 23 rng = date_range('1/1/1999', freq='H', periods=nhours) ser = Series(np.random.randn(len(rng)), rng) @@ -474,40 +453,40 @@ def test_finder_hourly(self): @slow def test_gaps(self): import matplotlib.pyplot as plt - plt.close('all') + ts = tm.makeTimeSeries() ts[5:25] = np.nan ax = ts.plot() lines = ax.get_lines() - self.assert_(len(lines) == 1) + self.assertEqual(len(lines), 1) l = lines[0] data = l.get_xydata() tm.assert_isinstance(data, np.ma.core.MaskedArray) mask = data.mask self.assert_(mask[5:25, 1].all()) + plt.close(ax.get_figure()) # irregular - plt.close('all') ts = tm.makeTimeSeries() ts = ts[[0, 1, 2, 5, 7, 9, 12, 15, 20]] ts[2:5] = np.nan ax = ts.plot() lines = ax.get_lines() - self.assert_(len(lines) == 1) + self.assertEqual(len(lines), 1) l = lines[0] data = l.get_xydata() tm.assert_isinstance(data, np.ma.core.MaskedArray) mask = data.mask self.assert_(mask[2:5, 1].all()) + plt.close(ax.get_figure()) # non-ts - plt.close('all') idx = [0, 1, 2, 5, 7, 9, 12, 15, 20] ser = Series(np.random.randn(len(idx)), idx) ser[2:5] = np.nan ax = ser.plot() lines = ax.get_lines() - self.assert_(len(lines) == 1) + self.assertEqual(len(lines), 1) l = lines[0] data = l.get_xydata() tm.assert_isinstance(data, np.ma.core.MaskedArray) @@ -516,8 +495,6 @@ def test_gaps(self): @slow def test_gap_upsample(self): - import matplotlib.pyplot as plt - plt.close('all') low = tm.makeTimeSeries() low[5:25] = np.nan ax = low.plot() @@ -526,8 +503,8 @@ def test_gap_upsample(self): s = Series(np.random.randn(len(idxh)), idxh) s.plot(secondary_y=True) lines = ax.get_lines() - self.assert_(len(lines) == 1) - self.assert_(len(ax.right_ax.get_lines()) == 1) + self.assertEqual(len(lines), 1) + self.assertEqual(len(ax.right_ax.get_lines()), 1) l = lines[0] data = l.get_xydata() tm.assert_isinstance(data, np.ma.core.MaskedArray) @@ -537,7 +514,7 @@ def test_gap_upsample(self): @slow def test_secondary_y(self): import matplotlib.pyplot as plt - plt.close('all') + ser = Series(np.random.randn(10)) ser2 = Series(np.random.randn(10)) ax = ser.plot(secondary_y=True).right_ax @@ -546,23 +523,21 @@ def test_secondary_y(self): l = ax.get_lines()[0] xp = Series(l.get_ydata(), l.get_xdata()) assert_series_equal(ser, xp) - self.assert_(ax.get_yaxis().get_ticks_position() == 'right') - self.assert_(not axes[0].get_yaxis().get_visible()) + self.assertEqual(ax.get_yaxis().get_ticks_position(), 'right') + self.assertFalse(axes[0].get_yaxis().get_visible()) + plt.close(fig) ax2 = ser2.plot() - self.assert_(ax2.get_yaxis().get_ticks_position() == 'left') + self.assertEqual(ax2.get_yaxis().get_ticks_position(), 'default') + plt.close(ax2.get_figure()) - plt.close('all') ax = ser2.plot() ax2 = ser.plot(secondary_y=True).right_ax self.assert_(ax.get_yaxis().get_visible()) - plt.close('all') - @slow def test_secondary_y_ts(self): import matplotlib.pyplot as plt - plt.close('all') idx = date_range('1/1/2000', periods=10) ser = Series(np.random.randn(10), idx) ser2 = Series(np.random.randn(10), idx) @@ -572,13 +547,14 @@ def test_secondary_y_ts(self): l = ax.get_lines()[0] xp = Series(l.get_ydata(), l.get_xdata()).to_timestamp() assert_series_equal(ser, xp) - self.assert_(ax.get_yaxis().get_ticks_position() == 'right') - self.assert_(not axes[0].get_yaxis().get_visible()) + self.assertEqual(ax.get_yaxis().get_ticks_position(), 'right') + self.assertFalse(axes[0].get_yaxis().get_visible()) + plt.close(fig) ax2 = ser2.plot() - self.assert_(ax2.get_yaxis().get_ticks_position() == 'left') + self.assertEqual(ax2.get_yaxis().get_ticks_position(), 'default') + plt.close(ax2.get_figure()) - plt.close('all') ax = ser2.plot() ax2 = ser.plot(secondary_y=True) self.assert_(ax.get_yaxis().get_visible()) @@ -588,50 +564,41 @@ def test_secondary_kde(self): _skip_if_no_scipy() import matplotlib.pyplot as plt - plt.close('all') ser = Series(np.random.randn(10)) ax = ser.plot(secondary_y=True, kind='density').right_ax fig = ax.get_figure() axes = fig.get_axes() - self.assert_(axes[1].get_yaxis().get_ticks_position() == 'right') + self.assertEqual(axes[1].get_yaxis().get_ticks_position(), 'right') @slow def test_secondary_bar(self): - import matplotlib.pyplot as plt - plt.close('all') ser = Series(np.random.randn(10)) ax = ser.plot(secondary_y=True, kind='bar') fig = ax.get_figure() axes = fig.get_axes() - self.assert_(axes[1].get_yaxis().get_ticks_position() == 'right') + self.assertEqual(axes[1].get_yaxis().get_ticks_position(), 'right') @slow def test_secondary_frame(self): - import matplotlib.pyplot as plt - plt.close('all') df = DataFrame(np.random.randn(5, 3), columns=['a', 'b', 'c']) axes = df.plot(secondary_y=['a', 'c'], subplots=True) - self.assert_(axes[0].get_yaxis().get_ticks_position() == 'right') - self.assert_(axes[1].get_yaxis().get_ticks_position() == 'default') - self.assert_(axes[2].get_yaxis().get_ticks_position() == 'right') + self.assertEqual(axes[0].get_yaxis().get_ticks_position(), 'right') + self.assertEqual(axes[1].get_yaxis().get_ticks_position(), 'default') + self.assertEqual(axes[2].get_yaxis().get_ticks_position(), 'right') @slow def test_secondary_bar_frame(self): - import matplotlib.pyplot as plt - plt.close('all') df = DataFrame(np.random.randn(5, 3), columns=['a', 'b', 'c']) axes = df.plot(kind='bar', secondary_y=['a', 'c'], subplots=True) - self.assert_(axes[0].get_yaxis().get_ticks_position() == 'right') - self.assert_(axes[1].get_yaxis().get_ticks_position() == 'default') - self.assert_(axes[2].get_yaxis().get_ticks_position() == 'right') + self.assertEqual(axes[0].get_yaxis().get_ticks_position(), 'right') + self.assertEqual(axes[1].get_yaxis().get_ticks_position(), 'default') + self.assertEqual(axes[2].get_yaxis().get_ticks_position(), 'right') - @slow def test_mixed_freq_regular_first(self): import matplotlib.pyplot as plt - plt.close('all') s1 = tm.makeTimeSeries() s2 = s1[[0, 5, 10, 11, 12, 13, 14, 15]] - s1.plot() + ax = s1.plot() ax2 = s2.plot(style='g') lines = ax2.get_lines() idx1 = lines[0].get_xdata() @@ -640,30 +607,24 @@ def test_mixed_freq_regular_first(self): self.assert_(idx2.equals(s2.index.to_period('B'))) left, right = ax2.get_xlim() pidx = s1.index.to_period() - self.assert_(left == pidx[0].ordinal) - self.assert_(right == pidx[-1].ordinal) - plt.close('all') + self.assertEqual(left, pidx[0].ordinal) + self.assertEqual(right, pidx[-1].ordinal) @slow def test_mixed_freq_irregular_first(self): import matplotlib.pyplot as plt - plt.close('all') s1 = tm.makeTimeSeries() s2 = s1[[0, 5, 10, 11, 12, 13, 14, 15]] s2.plot(style='g') ax = s1.plot() - self.assert_(not hasattr(ax, 'freq')) + self.assertFalse(hasattr(ax, 'freq')) lines = ax.get_lines() x1 = lines[0].get_xdata() assert_array_equal(x1, s2.index.asobject.values) x2 = lines[1].get_xdata() assert_array_equal(x2, s1.index.asobject.values) - plt.close('all') - @slow def test_mixed_freq_hf_first(self): - import matplotlib.pyplot as plt - plt.close('all') idxh = date_range('1/1/1999', periods=365, freq='D') idxl = date_range('1/1/1999', periods=12, freq='M') high = Series(np.random.randn(len(idxh)), idxh) @@ -671,27 +632,26 @@ def test_mixed_freq_hf_first(self): high.plot() ax = low.plot() for l in ax.get_lines(): - self.assert_(PeriodIndex(data=l.get_xdata()).freq == 'D') + self.assertEqual(PeriodIndex(data=l.get_xdata()).freq, 'D') @slow def test_mixed_freq_alignment(self): - import matplotlib.pyplot as plt ts_ind = date_range('2012-01-01 13:00', '2012-01-02', freq='H') ts_data = np.random.randn(12) ts = Series(ts_data, index=ts_ind) ts2 = ts.asfreq('T').interpolate() - plt.close('all') ax = ts.plot() ts2.plot(style='r') - self.assert_(ax.lines[0].get_xdata()[0] == ax.lines[1].get_xdata()[0]) + self.assertEqual(ax.lines[0].get_xdata()[0], + ax.lines[1].get_xdata()[0]) @slow def test_mixed_freq_lf_first(self): import matplotlib.pyplot as plt - plt.close('all') + idxh = date_range('1/1/1999', periods=365, freq='D') idxl = date_range('1/1/1999', periods=12, freq='M') high = Series(np.random.randn(len(idxh)), idxh) @@ -699,11 +659,11 @@ def test_mixed_freq_lf_first(self): low.plot(legend=True) ax = high.plot(legend=True) for l in ax.get_lines(): - self.assert_(PeriodIndex(data=l.get_xdata()).freq == 'D') + self.assertEqual(PeriodIndex(data=l.get_xdata()).freq, 'D') leg = ax.get_legend() - self.assert_(len(leg.texts) == 2) + self.assertEqual(len(leg.texts), 2) + plt.close(ax.get_figure()) - plt.close('all') idxh = date_range('1/1/1999', periods=240, freq='T') idxl = date_range('1/1/1999', periods=4, freq='H') high = Series(np.random.randn(len(idxh)), idxh) @@ -711,9 +671,8 @@ def test_mixed_freq_lf_first(self): low.plot() ax = high.plot() for l in ax.get_lines(): - self.assert_(PeriodIndex(data=l.get_xdata()).freq == 'T') + self.assertEqual(PeriodIndex(data=l.get_xdata()).freq, 'T') - @slow def test_mixed_freq_irreg_period(self): ts = tm.makeTimeSeries() irreg = ts[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 29]] @@ -724,8 +683,6 @@ def test_mixed_freq_irreg_period(self): @slow def test_to_weekly_resampling(self): - import matplotlib.pyplot as plt - plt.close('all') idxh = date_range('1/1/1999', periods=52, freq='W') idxl = date_range('1/1/1999', periods=12, freq='M') high = Series(np.random.randn(len(idxh)), idxh) @@ -737,8 +694,6 @@ def test_to_weekly_resampling(self): @slow def test_from_weekly_resampling(self): - import matplotlib.pyplot as plt - plt.close('all') idxh = date_range('1/1/1999', periods=52, freq='W') idxl = date_range('1/1/1999', periods=12, freq='M') high = Series(np.random.randn(len(idxh)), idxh) @@ -763,9 +718,6 @@ def test_irreg_dtypes(self): @slow def test_time(self): - import matplotlib.pyplot as plt - plt.close('all') - t = datetime(1, 1, 1, 3, 30, 0) deltas = np.random.randint(1, 20, 3).cumsum() ts = np.array([(t + timedelta(minutes=int(x))).time() for x in deltas]) @@ -783,7 +735,7 @@ def test_time(self): xp = l.get_text() if len(xp) > 0: rs = time(h, m, s).strftime('%H:%M:%S') - self.assert_(xp, rs) + self.assertEqual(xp, rs) # change xlim ax.set_xlim('1:30', '5:00') @@ -797,13 +749,10 @@ def test_time(self): xp = l.get_text() if len(xp) > 0: rs = time(h, m, s).strftime('%H:%M:%S') - self.assert_(xp, rs) + self.assertEqual(xp, rs) @slow def test_time_musec(self): - import matplotlib.pyplot as plt - plt.close('all') - t = datetime(1, 1, 1, 3, 30, 0) deltas = np.random.randint(1, 20, 3).cumsum() ts = np.array([(t + timedelta(microseconds=int(x))).time() @@ -823,12 +772,10 @@ def test_time_musec(self): xp = l.get_text() if len(xp) > 0: rs = time(h, m, s).strftime('%H:%M:%S.%f') - self.assert_(xp, rs) + self.assertEqual(xp, rs) @slow def test_secondary_upsample(self): - import matplotlib.pyplot as plt - plt.close('all') idxh = date_range('1/1/1999', periods=365, freq='D') idxl = date_range('1/1/1999', periods=12, freq='M') high = Series(np.random.randn(len(idxh)), idxh) @@ -836,9 +783,9 @@ def test_secondary_upsample(self): low.plot() ax = high.plot(secondary_y=True) for l in ax.get_lines(): - self.assert_(l.get_xdata().freq == 'D') + self.assertEqual(l.get_xdata().freq, 'D') for l in ax.right_ax.get_lines(): - self.assert_(l.get_xdata().freq == 'D') + self.assertEqual(l.get_xdata().freq, 'D') @slow def test_secondary_legend(self): @@ -851,54 +798,54 @@ def test_secondary_legend(self): df = tm.makeTimeDataFrame() ax = df.plot(secondary_y=['A', 'B']) leg = ax.get_legend() - self.assert_(len(leg.get_lines()) == 4) - self.assert_(leg.get_texts()[0].get_text() == 'A (right)') - self.assert_(leg.get_texts()[1].get_text() == 'B (right)') - self.assert_(leg.get_texts()[2].get_text() == 'C') - self.assert_(leg.get_texts()[3].get_text() == 'D') + self.assertEqual(len(leg.get_lines()), 4) + self.assertEqual(leg.get_texts()[0].get_text(), 'A (right)') + self.assertEqual(leg.get_texts()[1].get_text(), 'B (right)') + self.assertEqual(leg.get_texts()[2].get_text(), 'C') + self.assertEqual(leg.get_texts()[3].get_text(), 'D') self.assert_(ax.right_ax.get_legend() is None) colors = set() for line in leg.get_lines(): colors.add(line.get_color()) # TODO: color cycle problems - self.assert_(len(colors) == 4) + self.assertEqual(len(colors), 4) plt.clf() ax = fig.add_subplot(211) ax = df.plot(secondary_y=['A', 'C'], mark_right=False) leg = ax.get_legend() - self.assert_(len(leg.get_lines()) == 4) - self.assert_(leg.get_texts()[0].get_text() == 'A') - self.assert_(leg.get_texts()[1].get_text() == 'B') - self.assert_(leg.get_texts()[2].get_text() == 'C') - self.assert_(leg.get_texts()[3].get_text() == 'D') + self.assertEqual(len(leg.get_lines()), 4) + self.assertEqual(leg.get_texts()[0].get_text(), 'A') + self.assertEqual(leg.get_texts()[1].get_text(), 'B') + self.assertEqual(leg.get_texts()[2].get_text(), 'C') + self.assertEqual(leg.get_texts()[3].get_text(), 'D') plt.clf() ax = df.plot(kind='bar', secondary_y=['A']) leg = ax.get_legend() - self.assert_(leg.get_texts()[0].get_text() == 'A (right)') - self.assert_(leg.get_texts()[1].get_text() == 'B') + self.assertEqual(leg.get_texts()[0].get_text(), 'A (right)') + self.assertEqual(leg.get_texts()[1].get_text(), 'B') plt.clf() ax = df.plot(kind='bar', secondary_y=['A'], mark_right=False) leg = ax.get_legend() - self.assert_(leg.get_texts()[0].get_text() == 'A') - self.assert_(leg.get_texts()[1].get_text() == 'B') + self.assertEqual(leg.get_texts()[0].get_text(), 'A') + self.assertEqual(leg.get_texts()[1].get_text(), 'B') plt.clf() ax = fig.add_subplot(211) df = tm.makeTimeDataFrame() ax = df.plot(secondary_y=['C', 'D']) leg = ax.get_legend() - self.assert_(len(leg.get_lines()) == 4) + self.assertEqual(len(leg.get_lines()), 4) self.assert_(ax.right_ax.get_legend() is None) colors = set() for line in leg.get_lines(): colors.add(line.get_color()) # TODO: color cycle problems - self.assert_(len(colors) == 4) + self.assertEqual(len(colors), 4) # non-ts df = tm.makeDataFrame() @@ -906,29 +853,28 @@ def test_secondary_legend(self): ax = fig.add_subplot(211) ax = df.plot(secondary_y=['A', 'B']) leg = ax.get_legend() - self.assert_(len(leg.get_lines()) == 4) + self.assertEqual(len(leg.get_lines()), 4) self.assert_(ax.right_ax.get_legend() is None) colors = set() for line in leg.get_lines(): colors.add(line.get_color()) # TODO: color cycle problems - self.assert_(len(colors) == 4) + self.assertEqual(len(colors), 4) plt.clf() ax = fig.add_subplot(211) ax = df.plot(secondary_y=['C', 'D']) leg = ax.get_legend() - self.assert_(len(leg.get_lines()) == 4) + self.assertEqual(len(leg.get_lines()), 4) self.assert_(ax.right_ax.get_legend() is None) colors = set() for line in leg.get_lines(): colors.add(line.get_color()) # TODO: color cycle problems - self.assert_(len(colors) == 4) + self.assertEqual(len(colors), 4) - @slow def test_format_date_axis(self): rng = date_range('1/1/2012', periods=12, freq='M') df = DataFrame(np.random.randn(len(rng), 3), rng) @@ -936,14 +882,15 @@ def test_format_date_axis(self): xaxis = ax.get_xaxis() for l in xaxis.get_ticklabels(): if len(l.get_text()) > 0: - self.assert_(l.get_rotation() == 30) + self.assertEqual(l.get_rotation(), 30) @slow def test_ax_plot(self): + import matplotlib.pyplot as plt + x = DatetimeIndex(start='2012-01-02', periods=10, freq='D') y = lrange(len(x)) - import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) lines = ax.plot(x, y, label='Y') @@ -971,39 +918,45 @@ def test_mpl_nopandas(self): assert_array_equal(np.array([x.toordinal() for x in dates]), line2.get_xydata()[:, 0]) + def _check_plot_works(f, freq=None, series=None, *args, **kwargs): import matplotlib.pyplot as plt fig = plt.gcf() - plt.clf() - ax = fig.add_subplot(211) - orig_ax = kwargs.pop('ax', plt.gca()) - orig_axfreq = getattr(orig_ax, 'freq', None) - - ret = f(*args, **kwargs) - assert(ret is not None) # do something more intelligent - - ax = kwargs.pop('ax', plt.gca()) - if series is not None: - dfreq = series.index.freq - if isinstance(dfreq, DateOffset): - dfreq = dfreq.rule_code - if orig_axfreq is None: - assert(ax.freq == dfreq) - - if freq is not None and orig_axfreq is None: - assert(ax.freq == freq) - - ax = fig.add_subplot(212) + try: - kwargs['ax'] = ax + plt.clf() + ax = fig.add_subplot(211) + orig_ax = kwargs.pop('ax', plt.gca()) + orig_axfreq = getattr(orig_ax, 'freq', None) + ret = f(*args, **kwargs) - assert(ret is not None) # do something more intelligent - except Exception: - pass + assert ret is not None # do something more intelligent + + ax = kwargs.pop('ax', plt.gca()) + if series is not None: + dfreq = series.index.freq + if isinstance(dfreq, DateOffset): + dfreq = dfreq.rule_code + if orig_axfreq is None: + assert ax.freq == dfreq + + if freq is not None and orig_axfreq is None: + assert ax.freq == freq + + ax = fig.add_subplot(212) + try: + kwargs['ax'] = ax + ret = f(*args, **kwargs) + assert ret is not None # do something more intelligent + except Exception: + pass + + with ensure_clean() as path: + plt.savefig(path) + finally: + plt.close(fig) - with ensure_clean() as path: - plt.savefig(path) if __name__ == '__main__': nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'], From a67fa5b91961d28d2a8c4c533cb73ace98a8b31f Mon Sep 17 00:00:00 2001 From: Alex Rothberg Date: Sun, 18 Aug 2013 12:37:25 -0400 Subject: [PATCH 21/26] BUG: Fix for issue with PythonParser::_check_thousands. (GH4596) --- doc/source/release.rst | 4 +++- pandas/io/parsers.py | 2 +- pandas/io/tests/test_cparser.py | 12 +++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index d747505593c94..4224880d3fde0 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -429,7 +429,9 @@ Bug Fixes ``ascending`` was being interpreted as ``True`` (:issue:`4839`, :issue:`4846`) - Fixed ``Panel.tshift`` not working. Added `freq` support to ``Panel.shift`` (:issue:`4853`) - + - Fix an issue in TextFileReader w/ Python engine (i.e. PythonParser) + with thousands != "," (:issue:`4596`) + pandas 0.12.0 ------------- diff --git a/pandas/io/parsers.py b/pandas/io/parsers.py index 5554bef4acf98..c4ea76585df83 100644 --- a/pandas/io/parsers.py +++ b/pandas/io/parsers.py @@ -1538,7 +1538,7 @@ def _check_thousands(self, lines): nonnum.search(x.strip())): rl.append(x) else: - rl.append(x.replace(',', '')) + rl.append(x.replace(self.thousands, '')) ret.append(rl) return ret diff --git a/pandas/io/tests/test_cparser.py b/pandas/io/tests/test_cparser.py index d5f62cf909513..8db9c7de6cbcd 100644 --- a/pandas/io/tests/test_cparser.py +++ b/pandas/io/tests/test_cparser.py @@ -19,7 +19,7 @@ from pandas import DataFrame, Series, Index, isnull, MultiIndex import pandas.io.parsers as parsers from pandas.io.parsers import (read_csv, read_table, read_fwf, - TextParser) + TextParser, TextFileReader) from pandas.util.testing import (assert_almost_equal, assert_frame_equal, assert_series_equal, network) import pandas.lib as lib @@ -132,6 +132,16 @@ def test_integer_thousands(self): expected = [123456, 12500] tm.assert_almost_equal(result[0], expected) + + def test_integer_thousands_alt(self): + data = '123.456\n12.500' + + reader = TextFileReader(StringIO(data), delimiter=':', + thousands='.', header=None) + result = reader.read() + + expected = [123456, 12500] + tm.assert_almost_equal(result[0], expected) def test_skip_bad_lines(self): # too many lines, see #2430 for why From c051c35913effa24d0bc0903c5e66728db3b1f99 Mon Sep 17 00:00:00 2001 From: jreback Date: Thu, 19 Sep 2013 08:21:07 -0400 Subject: [PATCH 22/26] BUG: bug in getitem with a duplicate index when using where (GH4879) --- doc/source/release.rst | 6 ++++-- pandas/core/frame.py | 8 ++++++-- pandas/core/internals.py | 29 ++++++++++++++++++----------- pandas/tests/test_frame.py | 30 ++++++++++++++++++++++++++++++ pandas/tests/test_indexing.py | 5 +++-- 5 files changed, 61 insertions(+), 17 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index 4224880d3fde0..5a49f13cd8409 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -429,9 +429,11 @@ Bug Fixes ``ascending`` was being interpreted as ``True`` (:issue:`4839`, :issue:`4846`) - Fixed ``Panel.tshift`` not working. Added `freq` support to ``Panel.shift`` (:issue:`4853`) - - Fix an issue in TextFileReader w/ Python engine (i.e. PythonParser) + - Fix an issue in TextFileReader w/ Python engine (i.e. PythonParser) with thousands != "," (:issue:`4596`) - + - Bug in getitem with a duplicate index when using where (:issue:`4879`) + + pandas 0.12.0 ------------- diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 70fcc2c9d9c0a..cf0afe4267f69 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -1840,8 +1840,12 @@ def _getitem_column(self, key): if self.columns.is_unique: return self._get_item_cache(key) - # duplicate columns - return self._constructor(self._data.get(key)) + # duplicate columns & possible reduce dimensionaility + result = self._constructor(self._data.get(key)) + if result.columns.is_unique: + result = result[key] + + return result def _getitem_slice(self, key): return self._slice(key, axis=0) diff --git a/pandas/core/internals.py b/pandas/core/internals.py index 4b9fdb0422526..585b1c817ff19 100644 --- a/pandas/core/internals.py +++ b/pandas/core/internals.py @@ -401,7 +401,7 @@ def _astype(self, dtype, copy=False, raise_on_error=True, values=None, if values is None: values = com._astype_nansafe(self.values, dtype, copy=True) newb = make_block( - values, self.items, self.ref_items, ndim=self.ndim, + values, self.items, self.ref_items, ndim=self.ndim, placement=self._ref_locs, fastpath=True, dtype=dtype, klass=klass) except: if raise_on_error is True: @@ -716,7 +716,7 @@ def create_block(v, m, n, item, reshape=True): if inplace: return [self] - return [make_block(new_values, self.items, self.ref_items, fastpath=True)] + return [make_block(new_values, self.items, self.ref_items, placement=self._ref_locs, fastpath=True)] def interpolate(self, method='pad', axis=0, inplace=False, limit=None, fill_value=None, coerce=False, @@ -2853,12 +2853,13 @@ def _reindex_indexer_items(self, new_items, indexer, fill_value): # TODO: less efficient than I'd like item_order = com.take_1d(self.items.values, indexer) + new_axes = [new_items] + self.axes[1:] + new_blocks = [] + is_unique = new_items.is_unique # keep track of what items aren't found anywhere + l = np.arange(len(item_order)) mask = np.zeros(len(item_order), dtype=bool) - new_axes = [new_items] + self.axes[1:] - - new_blocks = [] for blk in self.blocks: blk_indexer = blk.items.get_indexer(item_order) selector = blk_indexer != -1 @@ -2872,12 +2873,19 @@ def _reindex_indexer_items(self, new_items, indexer, fill_value): new_block_items = new_items.take(selector.nonzero()[0]) new_values = com.take_nd(blk.values, blk_indexer[selector], axis=0, allow_fill=False) - new_blocks.append(make_block(new_values, new_block_items, - new_items, fastpath=True)) + placement = l[selector] if not is_unique else None + new_blocks.append(make_block(new_values, + new_block_items, + new_items, + placement=placement, + fastpath=True)) if not mask.all(): na_items = new_items[-mask] - na_block = self._make_na_block(na_items, new_items, + placement = l[-mask] if not is_unique else None + na_block = self._make_na_block(na_items, + new_items, + placement=placement, fill_value=fill_value) new_blocks.append(na_block) new_blocks = _consolidate(new_blocks, new_items) @@ -2943,7 +2951,7 @@ def reindex_items(self, new_items, indexer=None, copy=True, fill_value=None): return self.__class__(new_blocks, new_axes) - def _make_na_block(self, items, ref_items, fill_value=None): + def _make_na_block(self, items, ref_items, placement=None, fill_value=None): # TODO: infer dtypes other than float64 from fill_value if fill_value is None: @@ -2954,8 +2962,7 @@ def _make_na_block(self, items, ref_items, fill_value=None): dtype, fill_value = com._infer_dtype_from_scalar(fill_value) block_values = np.empty(block_shape, dtype=dtype) block_values.fill(fill_value) - na_block = make_block(block_values, items, ref_items) - return na_block + return make_block(block_values, items, ref_items, placement=placement) def take(self, indexer, new_index=None, axis=1, verify=True): if axis < 1: diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py index d216cebc1abf3..0bc454d6ef2bc 100644 --- a/pandas/tests/test_frame.py +++ b/pandas/tests/test_frame.py @@ -3150,6 +3150,36 @@ def check(result, expected=None): expected = DataFrame([[1],[1],[1]],columns=['bar']) check(df,expected) + def test_column_dups_indexing(self): + + def check(result, expected=None): + if expected is not None: + assert_frame_equal(result,expected) + result.dtypes + str(result) + + # boolean indexing + # GH 4879 + dups = ['A', 'A', 'C', 'D'] + df = DataFrame(np.arange(12).reshape(3,4), columns=['A', 'B', 'C', 'D'],dtype='float64') + expected = df[df.C > 6] + expected.columns = dups + df = DataFrame(np.arange(12).reshape(3,4), columns=dups,dtype='float64') + result = df[df.C > 6] + check(result,expected) + + # where + df = DataFrame(np.arange(12).reshape(3,4), columns=['A', 'B', 'C', 'D'],dtype='float64') + expected = df[df > 6] + expected.columns = dups + df = DataFrame(np.arange(12).reshape(3,4), columns=dups,dtype='float64') + result = df[df > 6] + check(result,expected) + + # boolean with the duplicate raises + df = DataFrame(np.arange(12).reshape(3,4), columns=dups,dtype='float64') + self.assertRaises(ValueError, lambda : df[df.A > 6]) + def test_insert_benchmark(self): # from the vb_suite/frame_methods/frame_insert_columns N = 10 diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py index 3453e69ed72b6..aad9fb2f95483 100644 --- a/pandas/tests/test_indexing.py +++ b/pandas/tests/test_indexing.py @@ -1233,9 +1233,10 @@ def test_mi_access(self): # GH 4146, not returning a block manager when selecting a unique index # from a duplicate index - expected = DataFrame([['a',1,1]],index=['A1'],columns=['h1','h3','h5'],).T + # as of 4879, this returns a Series (which is similar to what happens with a non-unique) + expected = Series(['a',1,1],index=['h1','h3','h5']) result = df2['A']['A1'] - assert_frame_equal(result,expected) + assert_series_equal(result,expected) # selecting a non_unique from the 2nd level expected = DataFrame([['d',4,4],['e',5,5]],index=Index(['B2','B2'],name='sub'),columns=['h1','h3','h5'],).T From ed1683f558ddd84e88d1859f9e13c7c792824619 Mon Sep 17 00:00:00 2001 From: Phillip Cloud Date: Thu, 19 Sep 2013 13:06:24 -0400 Subject: [PATCH 23/26] CLN: move README.rst to markdown --- README.md | 213 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.rst | 199 ------------------------------------------------- 2 files changed, 213 insertions(+), 199 deletions(-) create mode 100644 README.md delete mode 100644 README.rst diff --git a/README.md b/README.md new file mode 100644 index 0000000000000..e7a139ac26f0c --- /dev/null +++ b/README.md @@ -0,0 +1,213 @@ +# pandas: powerful Python data analysis toolkit + +![Travis-CI Build Status](https://travis-ci.org/pydata/pandas.png) + +## 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 +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. + +## Main Features +Here are just a few of the things that pandas does well: + + - Easy handling of [**missing data**][missing-data] (represented as + `NaN`) in floating point as well as non-floating point data + - Size mutability: columns can be [**inserted and + deleted**][insertion-deletion] from DataFrame and higher dimensional + objects + - Automatic and explicit [**data alignment**][alignment]: objects can + be explicitly aligned to a set of labels, or the user can simply + ignore the labels and let `Series`, `DataFrame`, etc. automatically + align the data for you in computations + - Powerful, flexible [**group by**][groupby] functionality to perform + split-apply-combine operations on data sets, for both aggregating + and transforming data + - Make it [**easy to convert**][conversion] ragged, + differently-indexed data in other Python and NumPy data structures + into DataFrame objects + - Intelligent label-based [**slicing**][slicing], [**fancy + indexing**][fancy-indexing], and [**subsetting**][subsetting] of + large data sets + - Intuitive [**merging**][merging] and [**joining**][joining] data + sets + - Flexible [**reshaping**][reshape] and [**pivoting**][pivot-table] of + data sets + - [**Hierarchical**][mi] labeling of axes (possible to have multiple + labels per tick) + - Robust IO tools for loading data from [**flat files**][flat-files] + (CSV and delimited), [**Excel files**][excel], [**databases**][db], + and saving/loading data from the ultrafast [**HDF5 format**][hdfstore] + - [**Time series**][timeseries]-specific functionality: date range + generation and frequency conversion, moving window statistics, + moving window linear regressions, date shifting and lagging, etc. + + + [missing-data]: http://pandas.pydata.org/pandas-docs/stable/missing_data.html#working-with-missing-data + [insertion-deletion]: http://pandas.pydata.org/pandas-docs/stable/dsintro.html#column-selection-addition-deletion + [alignment]: http://pandas.pydata.org/pandas-docs/stable/dsintro.html?highlight=alignment#intro-to-data-structures + [groupby]: http://pandas.pydata.org/pandas-docs/stable/groupby.html#group-by-split-apply-combine + [conversion]: http://pandas.pydata.org/pandas-docs/stable/dsintro.html#dataframe + [slicing]: http://pandas.pydata.org/pandas-docs/stable/indexing.html#slicing-ranges + [fancy-indexing]: http://pandas.pydata.org/pandas-docs/stable/indexing.html#advanced-indexing-with-ix + [subsetting]: http://pandas.pydata.org/pandas-docs/stable/indexing.html#boolean-indexing + [merging]: http://pandas.pydata.org/pandas-docs/stable/merging.html#database-style-dataframe-joining-merging + [joining]: http://pandas.pydata.org/pandas-docs/stable/merging.html#joining-on-index + [reshape]: http://pandas.pydata.org/pandas-docs/stable/reshaping.html#reshaping-and-pivot-tables + [pivot-table]: http://pandas.pydata.org/pandas-docs/stable/reshaping.html#pivot-tables-and-cross-tabulations + [mi]: http://pandas.pydata.org/pandas-docs/stable/indexing.html#hierarchical-indexing-multiindex + [flat-files]: http://pandas.pydata.org/pandas-docs/stable/io.html#csv-text-files + [excel]: http://pandas.pydata.org/pandas-docs/stable/io.html#excel-files + [db]: http://pandas.pydata.org/pandas-docs/stable/io.html#sql-queries + [hdfstore]: http://pandas.pydata.org/pandas-docs/stable/io.html#hdf5-pytables + [timeseries]: http://pandas.pydata.org/pandas-docs/stable/timeseries.html#time-series-date-functionality + +## Where to get it +The source code is currently hosted on GitHub at: +http://github.com/pydata/pandas + +Binary installers for the latest released version are available at the Python +package index + + http://pypi.python.org/pypi/pandas/ + +And via `easy_install`: + +```sh +easy_install pandas +``` + +or `pip`: + +```sh +pip install pandas +``` + +## Dependencies +- [NumPy](http://www.numpy.org): 1.6.1 or higher +- [python-dateutil](http://labix.org/python-dateutil): 1.5 or higher +- [pytz](http://pytz.sourceforge.net) + - Needed for time zone support with ``pandas.date_range`` + +### Highly Recommended Dependencies +- [numexpr](http://code.google.com/p/numexpr/) + - Needed to accelerate some expression evaluation operations + - Required by PyTables +- [bottleneck](http://berkeleyanalytics.com/bottleneck) + - Needed to accelerate certain numerical operations + +### Optional dependencies +- [Cython](http://www.cython.org): Only necessary to build development version. Version 0.17.1 or higher. +- [SciPy](http://www.scipy.org): miscellaneous statistical functions +- [PyTables](http://www.pytables.org): necessary for HDF5-based storage +- [matplotlib](http://matplotlib.sourceforge.net/): for plotting +- [statsmodels](http://statsmodels.sourceforge.net/) + - Needed for parts of `pandas.stats` +- [openpyxl](http://packages.python.org/openpyxl/), [xlrd/xlwt](http://www.python-excel.org/) + - openpyxl version 1.6.1 or higher, for writing .xlsx files + - xlrd >= 0.9.0 + - Needed for Excel I/O +- [boto](https://pypi.python.org/pypi/boto): necessary for Amazon S3 access. +- One of the following combinations of libraries is needed to use the + top-level [`pandas.read_html`][read-html-docs] function: + - [BeautifulSoup4][BeautifulSoup4] and [html5lib][html5lib] (Any + recent version of [html5lib][html5lib] is okay.) + - [BeautifulSoup4][BeautifulSoup4] and [lxml][lxml] + - [BeautifulSoup4][BeautifulSoup4] and [html5lib][html5lib] and [lxml][lxml] + - Only [lxml][lxml], although see [HTML reading gotchas][html-gotchas] + for reasons as to why you should probably **not** take this approach. + +#### Notes about HTML parsing libraries +- If you install [BeautifulSoup4][BeautifulSoup4] you must install + either [lxml][lxml] or [html5lib][html5lib] or both. + `pandas.read_html` will **not** work with *only* `BeautifulSoup4` + installed. +- You are strongly encouraged to read [HTML reading + gotchas][html-gotchas]. It explains issues surrounding the + installation and usage of the above three libraries. +- You may need to install an older version of + [BeautifulSoup4][BeautifulSoup4]: + - Versions 4.2.1, 4.1.3 and 4.0.2 have been confirmed for 64 and + 32-bit Ubuntu/Debian +- Additionally, if you're using [Anaconda][Anaconda] you should + definitely read [the gotchas about HTML parsing][html-gotchas] + libraries +- If you're on a system with `apt-get` you can do + + ```sh + sudo apt-get build-dep python-lxml + ``` + + to get the necessary dependencies for installation of [lxml][lxml]. + This will prevent further headaches down the line. + + [html5lib]: https://github.com/html5lib/html5lib-python "html5lib" + [BeautifulSoup4]: http://www.crummy.com/software/BeautifulSoup "BeautifulSoup4" + [lxml]: http://lxml.de + [Anaconda]: https://store.continuum.io/cshop/anaconda + [NumPy]: http://numpy.scipy.org/ + [html-gotchas]: http://pandas.pydata.org/pandas-docs/stable/gotchas.html#html-table-parsing + [read-html-docs]: http://pandas.pydata.org/pandas-docs/stable/generated/pandas.io.html.read_html.html#pandas.io.html.read_html + +## Installation from sources +To install pandas from source you need Cython in addition to the normal +dependencies above. Cython can be installed from pypi: + +```sh +pip install cython +``` + +In the `pandas` directory (same one where you found this file after +cloning the git repo), execute: + +```sh +python setup.py install +``` + +or for installing in [development mode](http://www.pip-installer.org/en/latest/usage.html): + +```sh +python setup.py develop +``` + +Alternatively, you can use `pip` if you want all the dependencies pulled +in automatically (the `-e` option is for installing it in [development +mode](http://www.pip-installer.org/en/latest/usage.html)): + +```sh +pip install -e . +``` + +On Windows, you will need to install MinGW and execute: + +```sh +python setup.py build --compiler=mingw32 +python setup.py install +``` + +See http://pandas.pydata.org/ for more information. + +## License +BSD + +## Documentation +The official documentation is hosted on PyData.org: http://pandas.pydata.org/ + +The Sphinx documentation should provide a good starting point for learning how +to use the library. Expect the docs to continue to expand as time goes on. + +## Background +Work on ``pandas`` started at AQR (a quantitative hedge fund) in 2008 and +has been under active development since then. + +## Discussion and Development +Since pandas development is related to a number of other scientific +Python projects, questions are welcome on the scipy-user mailing +list. Specialized discussions or design issues should take place on +the pystatsmodels mailing list / Google group, where +``scikits.statsmodels`` and other libraries will also be discussed: + +http://groups.google.com/group/pystatsmodels diff --git a/README.rst b/README.rst deleted file mode 100644 index da789e704ebad..0000000000000 --- a/README.rst +++ /dev/null @@ -1,199 +0,0 @@ -============================================= -pandas: powerful Python data analysis toolkit -============================================= - -.. image:: https://travis-ci.org/pydata/pandas.png - :target: https://travis-ci.org/pydata/pandas - -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 -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. - -Main Features -============= - -Here are just a few of the things that pandas does well: - - - Easy handling of **missing data** (represented as NaN) in floating point as - well as non-floating point data - - Size mutability: columns can be **inserted and deleted** from DataFrame and - higher dimensional objects - - Automatic and explicit **data alignment**: objects can be explicitly - aligned to a set of labels, or the user can simply ignore the labels and - let `Series`, `DataFrame`, etc. automatically align the data for you in - computations - - Powerful, flexible **group by** functionality to perform - split-apply-combine operations on data sets, for both aggregating and - transforming data - - Make it **easy to convert** ragged, differently-indexed data in other - Python and NumPy data structures into DataFrame objects - - Intelligent label-based **slicing**, **fancy indexing**, and **subsetting** - of large data sets - - Intuitive **merging** and **joining** data sets - - Flexible **reshaping** and pivoting of data sets - - **Hierarchical** labeling of axes (possible to have multiple labels per - tick) - - Robust IO tools for loading data from **flat files** (CSV and delimited), - Excel files, databases, and saving / loading data from the ultrafast **HDF5 - format** - - **Time series**-specific functionality: date range generation and frequency - conversion, moving window statistics, moving window linear regressions, - date shifting and lagging, etc. - -Where to get it -=============== - -The source code is currently hosted on GitHub at: http://github.com/pydata/pandas - -Binary installers for the latest released version are available at the Python -package index:: - - http://pypi.python.org/pypi/pandas/ - -And via ``easy_install`` or ``pip``:: - - easy_install pandas - pip install pandas - -Dependencies -============ - - - `NumPy `__: 1.6.1 or higher - - `python-dateutil `__ 1.5 or higher - - `pytz `__ - - Needed for time zone support with ``date_range`` - -Highly Recommended Dependencies -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - - `numexpr `__ - - Needed to accelerate some expression evaluation operations - - Required by `PyTables` - - `bottleneck `__ - - Needed to accelerate certain numerical operations - -Optional dependencies -~~~~~~~~~~~~~~~~~~~~~ - - - `Cython `__: Only necessary to build development version. Version 0.17.1 or higher. - - `SciPy `__: miscellaneous statistical functions - - `PyTables `__: necessary for HDF5-based storage - - `matplotlib `__: for plotting - - `statsmodels `__ - - Needed for parts of :mod:`pandas.stats` - - `openpyxl `__, `xlrd/xlwt `__ - - openpyxl version 1.6.1 or higher, for writing .xlsx files - - xlrd >= 0.9.0 - - Needed for Excel I/O - - `boto `__: necessary for Amazon S3 - access. - - One of the following combinations of libraries is needed to use the - top-level :func:`~pandas.io.html.read_html` function: - - - `BeautifulSoup4`_ and `html5lib`_ (Any recent version of `html5lib`_ is - okay.) - - `BeautifulSoup4`_ and `lxml`_ - - `BeautifulSoup4`_ and `html5lib`_ and `lxml`_ - - Only `lxml`_, although see :ref:`HTML reading gotchas ` - for reasons as to why you should probably **not** take this approach. - - .. warning:: - - - if you install `BeautifulSoup4`_ you must install either - `lxml`_ or `html5lib`_ or both. - :func:`~pandas.io.html.read_html` will **not** work with *only* - `BeautifulSoup4`_ installed. - - You are highly encouraged to read :ref:`HTML reading gotchas - `. It explains issues surrounding the installation and - usage of the above three libraries - - You may need to install an older version of `BeautifulSoup4`_: - - Versions 4.2.1, 4.1.3 and 4.0.2 have been confirmed for 64 and - 32-bit Ubuntu/Debian - - Additionally, if you're using `Anaconda`_ you should definitely - read :ref:`the gotchas about HTML parsing libraries ` - - .. note:: - - - if you're on a system with ``apt-get`` you can do - - .. code-block:: sh - - sudo apt-get build-dep python-lxml - - to get the necessary dependencies for installation of `lxml`_. This - will prevent further headaches down the line. - - -.. _html5lib: https://github.com/html5lib/html5lib-python -.. _BeautifulSoup4: http://www.crummy.com/software/BeautifulSoup -.. _lxml: http://lxml.de -.. _Anaconda: https://store.continuum.io/cshop/anaconda - - -Installation from sources -========================= - -To install pandas from source you need ``cython`` in addition to the normal dependencies above, -which can be installed from pypi:: - - pip install cython - -In the ``pandas`` directory (same one where you found this file after cloning the git repo), execute:: - - python setup.py install - -or for installing in `development mode `__:: - - python setup.py develop - -Alternatively, you can use `pip` if you want all the dependencies pulled in automatically -(the optional ``-e`` option is for installing it in -`development mode `__):: - - pip install -e . - -On Windows, you will need to install MinGW and execute:: - - python setup.py build --compiler=mingw32 - python setup.py install - -See http://pandas.pydata.org/ for more information. - -License -======= - -BSD - -Documentation -============= - -The official documentation is hosted on PyData.org: http://pandas.pydata.org/ - -The Sphinx documentation should provide a good starting point for learning how -to use the library. Expect the docs to continue to expand as time goes on. - -Background -========== - -Work on ``pandas`` started at AQR (a quantitative hedge fund) in 2008 and -has been under active development since then. - -Discussion and Development -========================== - -Since ``pandas`` development is related to a number of other scientific -Python projects, questions are welcome on the scipy-user mailing -list. Specialized discussions or design issues should take place on -the pystatsmodels mailing list / Google group, where -``scikits.statsmodels`` and other libraries will also be discussed: - -http://groups.google.com/group/pystatsmodels - - .. _NumPy: http://numpy.scipy.org/ From 5f05f005ad456c586c24d22ecbed68f2842bb0c5 Mon Sep 17 00:00:00 2001 From: Valentin Haenel Date: Tue, 6 Aug 2013 21:28:00 +0200 Subject: [PATCH 24/26] BUG: off-by-one when doing network test repeats Imagine num_runs is two, then runs will take on the values [0, 1] and runs < num_uns will always be False. Hence any genuine Exception the code might raise will be silently ignored and just printed. --- pandas/util/testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 0481a522dabe8..e7e930320116b 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -860,7 +860,7 @@ def network_wrapper(*args, **kwargs): except SkipTest: raise except Exception as e: - if runs < num_runs: + if runs < num_runs - 1: print("Failed: %r" % e) else: raise From edbf949a31ffa4a36fc690af451baa1d9a2ca01c Mon Sep 17 00:00:00 2001 From: Valentin Haenel Date: Tue, 6 Aug 2013 23:12:30 +0200 Subject: [PATCH 25/26] BUG/TST: fix (and skip) tests that have been failing unnoticed all along The test is unstable and although this makes it work properly, no one knows how long. Hence it was decided to skip this guy until further notice. See also: #4427 Skip test_fred_nan and test_fred_parts, both of which have been failing silently for a while --- pandas/io/tests/test_data.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pandas/io/tests/test_data.py b/pandas/io/tests/test_data.py index 0e428ae4d0310..dd0b47bcfb90a 100644 --- a/pandas/io/tests/test_data.py +++ b/pandas/io/tests/test_data.py @@ -368,18 +368,20 @@ def test_fred(self): Throws an exception when DataReader can't get a 200 response from FRED. """ + + raise nose.SkipTest('Skip as this is unstable #4427 ') start = datetime(2010, 1, 1) end = datetime(2013, 1, 27) - self.assertEquals( - web.DataReader("GDP", "fred", start, end)['GDP'].tail(1), - 15984.1) + received = web.DataReader("GDP", "fred", start, end)['GDP'].tail(1)[0] + self.assertEquals(int(received), 16535) self.assertRaises(Exception, web.DataReader, "NON EXISTENT SERIES", 'fred', start, end) @network def test_fred_nan(self): + raise nose.SkipTest("Unstable test case - needs to be fixed.") start = datetime(2010, 1, 1) end = datetime(2013, 1, 27) df = web.DataReader("DFII5", "fred", start, end) @@ -387,6 +389,7 @@ def test_fred_nan(self): @network def test_fred_parts(self): + raise nose.SkipTest("Unstable test case - needs to be fixed.") start = datetime(2010, 1, 1) end = datetime(2013, 1, 27) df = web.get_data_fred("CPIAUCSL", start, end) From fda172d06ae118ad28c42c4cf9b364b2a8e621c1 Mon Sep 17 00:00:00 2001 From: TomAugspurger Date: Thu, 19 Sep 2013 23:00:47 -0500 Subject: [PATCH 26/26] BUG/TST: Fix failing FRED comparison tests. Fixes #4827 Assertions were being made about arrays (of one item) equaling a scaler. --- pandas/io/tests/test_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/tests/test_data.py b/pandas/io/tests/test_data.py index 4b2d99dfd176f..dd0b47bcfb90a 100644 --- a/pandas/io/tests/test_data.py +++ b/pandas/io/tests/test_data.py @@ -385,7 +385,7 @@ def test_fred_nan(self): start = datetime(2010, 1, 1) end = datetime(2013, 1, 27) df = web.DataReader("DFII5", "fred", start, end) - assert pd.isnull(df.ix['2010-01-01']) + assert pd.isnull(df.ix['2010-01-01'][0]) @network def test_fred_parts(self): @@ -393,7 +393,7 @@ def test_fred_parts(self): start = datetime(2010, 1, 1) end = datetime(2013, 1, 27) df = web.get_data_fred("CPIAUCSL", start, end) - self.assertEqual(df.ix['2010-05-01'], 217.23) + self.assertEqual(df.ix['2010-05-01'][0], 217.23) t = df.CPIAUCSL.values assert np.issubdtype(t.dtype, np.floating)