Skip to content

Commit b3f16da

Browse files
committed
Merge pull request #3912 from cpcloud/try-convert-when-plotting
FIX/ENH: attempt soft conversion of object series before raising a TypeError when plotting
2 parents e4b8ed4 + cc94708 commit b3f16da

File tree

7 files changed

+136
-51
lines changed

7 files changed

+136
-51
lines changed

RELEASE.rst

+8-6
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ pandas 0.11.1
7777
dependencies offered for Linux) (GH3837_).
7878
- Plotting functions now raise a ``TypeError`` before trying to plot anything
7979
if the associated objects have have a dtype of ``object`` (GH1818_,
80-
GH3572_). This happens before any drawing takes place which elimnates any
81-
spurious plots from showing up.
80+
GH3572_, GH3911_, GH3912_), but they will try to convert object arrays to
81+
numeric arrays if possible so that you can still plot, for example, an
82+
object array with floats. This happens before any drawing takes place which
83+
elimnates any spurious plots from showing up.
8284
- Added Faq section on repr display options, to help users customize their setup.
8385
- ``where`` operations that result in block splitting are much faster (GH3733_)
8486
- Series and DataFrame hist methods now take a ``figsize`` argument (GH3834_)
@@ -341,13 +343,13 @@ pandas 0.11.1
341343
.. _GH3834: https://github.com/pydata/pandas/issues/3834
342344
.. _GH3873: https://github.com/pydata/pandas/issues/3873
343345
.. _GH3877: https://github.com/pydata/pandas/issues/3877
346+
.. _GH3659: https://github.com/pydata/pandas/issues/3659
347+
.. _GH3679: https://github.com/pydata/pandas/issues/3679
344348
.. _GH3880: https://github.com/pydata/pandas/issues/3880
345-
<<<<<<< HEAD
346349
.. _GH3911: https://github.com/pydata/pandas/issues/3911
347-
=======
348350
.. _GH3907: https://github.com/pydata/pandas/issues/3907
349-
>>>>>>> 7b5933247b80174de4ba571e95a1add809dd9d09
350-
351+
.. _GH3911: https://github.com/pydata/pandas/issues/3911
352+
.. _GH3912: https://github.com/pydata/pandas/issues/3912
351353

352354
pandas 0.11.0
353355
=============

doc/source/v0.11.1.txt

+7-3
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,11 @@ Bug Fixes
300300
~~~~~~~~~
301301

302302
- Plotting functions now raise a ``TypeError`` before trying to plot anything
303-
if the associated objects have have a ``dtype`` of ``object`` (GH1818_,
304-
GH3572_). This happens before any drawing takes place which elimnates any
305-
spurious plots from showing up.
303+
if the associated objects have have a dtype of ``object`` (GH1818_,
304+
GH3572_, GH3911_, GH3912_), but they will try to convert object arrays to
305+
numeric arrays if possible so that you can still plot, for example, an
306+
object array with floats. This happens before any drawing takes place which
307+
elimnates any spurious plots from showing up.
306308

307309
- ``fillna`` methods now raise a ``TypeError`` if the ``value`` parameter is
308310
a list or tuple.
@@ -416,3 +418,5 @@ on GitHub for a complete list.
416418
.. _GH3659: https://github.com/pydata/pandas/issues/3659
417419
.. _GH3679: https://github.com/pydata/pandas/issues/3679
418420
.. _GH3907: https://github.com/pydata/pandas/issues/3907
421+
.. _GH3911: https://github.com/pydata/pandas/issues/3911
422+
.. _GH3912: https://github.com/pydata/pandas/issues/3912

pandas/io/common.py

+20-14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
_VALID_URLS.discard('')
1010

1111

12+
class PerformanceWarning(Warning):
13+
pass
14+
15+
1216
def _is_url(url):
1317
"""Check to see if a URL has a valid protocol.
1418
@@ -26,27 +30,29 @@ def _is_url(url):
2630
except:
2731
return False
2832

33+
2934
def _is_s3_url(url):
30-
""" Check for an s3 url """
35+
"""Check for an s3 url"""
3136
try:
3237
return urlparse.urlparse(url).scheme == 's3'
3338
except:
3439
return False
3540

41+
3642
def get_filepath_or_buffer(filepath_or_buffer, encoding=None):
37-
""" if the filepath_or_buffer is a url, translate and return the buffer
38-
passthru otherwise
39-
40-
Parameters
41-
----------
42-
filepath_or_buffer : a url, filepath, or buffer
43-
encoding : the encoding to use to decode py3 bytes, default is 'utf-8'
44-
45-
Returns
46-
-------
47-
a filepath_or_buffer, the encoding
48-
49-
"""
43+
"""
44+
If the filepath_or_buffer is a url, translate and return the buffer
45+
passthru otherwise.
46+
47+
Parameters
48+
----------
49+
filepath_or_buffer : a url, filepath, or buffer
50+
encoding : the encoding to use to decode py3 bytes, default is 'utf-8'
51+
52+
Returns
53+
-------
54+
a filepath_or_buffer, the encoding
55+
"""
5056

5157
if _is_url(filepath_or_buffer):
5258
from urllib2 import urlopen

pandas/io/pytables.py

+30-17
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,22 @@
1212
import warnings
1313

1414
import numpy as np
15-
from pandas import (
16-
Series, TimeSeries, DataFrame, Panel, Panel4D, Index,
17-
MultiIndex, Int64Index, Timestamp
18-
)
15+
from pandas import (Series, TimeSeries, DataFrame, Panel, Panel4D, Index,
16+
MultiIndex, Int64Index, Timestamp)
1917
from pandas.sparse.api import SparseSeries, SparseDataFrame, SparsePanel
2018
from pandas.sparse.array import BlockIndex, IntIndex
2119
from pandas.tseries.api import PeriodIndex, DatetimeIndex
22-
from pandas.core.common import adjoin, isnull, is_list_like
23-
from pandas.core.algorithms import match, unique, factorize
20+
from pandas.core.common import adjoin, is_list_like
21+
from pandas.core.algorithms import match, unique
2422
from pandas.core.categorical import Categorical
25-
from pandas.core.common import _asarray_tuplesafe, _try_sort
23+
from pandas.core.common import _asarray_tuplesafe
2624
from pandas.core.internals import BlockManager, make_block
2725
from pandas.core.reshape import block2d_to_blocknd, factor_indexer
28-
from pandas.core.index import Int64Index, _ensure_index
26+
from pandas.core.index import _ensure_index
2927
import pandas.core.common as com
3028
from pandas.tools.merge import concat
3129
from pandas.util import py3compat
30+
from pandas.io.common import PerformanceWarning
3231

3332
import pandas.lib as lib
3433
import pandas.algos as algos
@@ -42,32 +41,46 @@
4241
# PY3 encoding if we don't specify
4342
_default_encoding = 'UTF-8'
4443

44+
4545
def _ensure_decoded(s):
4646
""" if we have bytes, decode them to unicde """
4747
if isinstance(s, np.bytes_):
4848
s = s.decode('UTF-8')
4949
return s
50+
51+
5052
def _ensure_encoding(encoding):
5153
# set the encoding if we need
5254
if encoding is None:
5355
if py3compat.PY3:
5456
encoding = _default_encoding
5557
return encoding
5658

57-
class IncompatibilityWarning(Warning): pass
59+
60+
class IncompatibilityWarning(Warning):
61+
pass
62+
63+
5864
incompatibility_doc = """
59-
where criteria is being ignored as this version [%s] is too old (or not-defined),
60-
read the file in and write it out to a new file to upgrade (with the copy_to method)
65+
where criteria is being ignored as this version [%s] is too old (or
66+
not-defined), read the file in and write it out to a new file to upgrade (with
67+
the copy_to method)
6168
"""
62-
class AttributeConflictWarning(Warning): pass
69+
70+
71+
class AttributeConflictWarning(Warning):
72+
pass
73+
74+
6375
attribute_conflict_doc = """
64-
the [%s] attribute of the existing index is [%s] which conflicts with the new [%s],
65-
resetting the attribute to None
76+
the [%s] attribute of the existing index is [%s] which conflicts with the new
77+
[%s], resetting the attribute to None
6678
"""
67-
class PerformanceWarning(Warning): pass
79+
80+
6881
performance_doc = """
69-
your performance may suffer as PyTables will pickle object types that it cannot map
70-
directly to c-types [inferred_type->%s,key->%s] [items->%s]
82+
your performance may suffer as PyTables will pickle object types that it cannot
83+
map directly to c-types [inferred_type->%s,key->%s] [items->%s]
7184
"""
7285

7386
# map object types

pandas/tests/test_graphics.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from pandas.util.testing import ensure_clean
1111
from pandas.core.config import set_option
1212

13+
1314
import numpy as np
1415

1516
from numpy.testing import assert_array_equal
@@ -189,15 +190,22 @@ def test_bootstrap_plot(self):
189190
from pandas.tools.plotting import bootstrap_plot
190191
_check_plot_works(bootstrap_plot, self.ts, size=10)
191192

192-
@slow
193-
def test_all_invalid_plot_data(self):
193+
def test_invalid_plot_data(self):
194194
s = Series(list('abcd'))
195195
kinds = 'line', 'bar', 'barh', 'kde', 'density'
196196

197197
for kind in kinds:
198198
self.assertRaises(TypeError, s.plot, kind=kind)
199199

200200
@slow
201+
def test_valid_object_plot(self):
202+
from pandas.io.common import PerformanceWarning
203+
s = Series(range(10), dtype=object)
204+
kinds = 'line', 'bar', 'barh', 'kde', 'density'
205+
206+
for kind in kinds:
207+
_check_plot_works(s.plot, kind=kind)
208+
201209
def test_partially_invalid_plot_data(self):
202210
s = Series(['a', 'b', 1.0, 2])
203211
kinds = 'line', 'bar', 'barh', 'kde', 'density'

pandas/tools/plotting.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -878,15 +878,20 @@ def _get_layout(self):
878878

879879
def _compute_plot_data(self):
880880
try:
881-
# might be a frame
881+
# might be an ndframe
882882
numeric_data = self.data._get_numeric_data()
883-
except AttributeError:
884-
# a series, but no object dtypes allowed!
885-
if self.data.dtype == np.object_:
886-
raise TypeError('invalid dtype for plotting, please cast to a '
887-
'numeric dtype explicitly if you want to plot')
888-
883+
except AttributeError: # TODO: rm in 0.12 (series-inherit-ndframe)
889884
numeric_data = self.data
885+
orig_dtype = numeric_data.dtype
886+
887+
# possible object array of numeric data
888+
if orig_dtype == np.object_:
889+
numeric_data = numeric_data.convert_objects() # soft convert
890+
891+
# still an object dtype so we can't plot it
892+
if numeric_data.dtype == np.object_:
893+
raise TypeError('Series has object dtype and cannot be'
894+
' converted: no numeric data to plot')
890895

891896
try:
892897
is_empty = numeric_data.empty
@@ -895,7 +900,8 @@ def _compute_plot_data(self):
895900

896901
# no empty frames or series allowed
897902
if is_empty:
898-
raise TypeError('No numeric data to plot')
903+
raise TypeError('Empty {0!r}: no numeric data to '
904+
'plot'.format(numeric_data.__class__.__name__))
899905

900906
self.data = numeric_data
901907

pandas/util/testing.py

+47-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import string
88
import sys
99
import tempfile
10+
import warnings
1011

1112
from contextlib import contextmanager # contextlib is available since 2.5
1213

@@ -39,7 +40,7 @@
3940

4041
def rands(n):
4142
choices = string.ascii_letters + string.digits
42-
return ''.join([random.choice(choices) for _ in xrange(n)])
43+
return ''.join(random.choice(choices) for _ in xrange(n))
4344

4445

4546
def randu(n):
@@ -746,3 +747,48 @@ def stdin_encoding(encoding=None):
746747
sys.stdin = SimpleMock(sys.stdin, "encoding", encoding)
747748
yield
748749
sys.stdin = _stdin
750+
751+
752+
@contextmanager
753+
def assert_produces_warning(expected_warning=Warning, filter_level="always"):
754+
"""
755+
Context manager for running code that expects to raise (or not raise)
756+
warnings. Checks that code raises the expected warning and only the
757+
expected warning. Pass ``False`` or ``None`` to check that it does *not*
758+
raise a warning. Defaults to ``exception.Warning``, baseclass of all
759+
Warnings. (basically a wrapper around ``warnings.catch_warnings``).
760+
761+
>>> import warnings
762+
>>> with assert_produces_warning():
763+
... warnings.warn(UserWarning())
764+
...
765+
>>> with assert_produces_warning(False):
766+
... warnings.warn(RuntimeWarning())
767+
...
768+
Traceback (most recent call last):
769+
...
770+
AssertionError: Caused unexpected warning(s): ['RuntimeWarning'].
771+
>>> with assert_produces_warning(UserWarning):
772+
... warnings.warn(RuntimeWarning())
773+
Traceback (most recent call last):
774+
...
775+
AssertionError: Did not see expected warning of class 'UserWarning'.
776+
777+
..warn:: This is *not* thread-safe.
778+
"""
779+
with warnings.catch_warnings(record=True) as w:
780+
saw_warning = False
781+
warnings.simplefilter(filter_level)
782+
yield w
783+
extra_warnings = []
784+
for actual_warning in w:
785+
if (expected_warning and issubclass(actual_warning.category,
786+
expected_warning)):
787+
saw_warning = True
788+
else:
789+
extra_warnings.append(actual_warning.category.__name__)
790+
if expected_warning:
791+
assert saw_warning, ("Did not see expected warning of class %r."
792+
% expected_warning.__name__)
793+
assert not extra_warnings, ("Caused unexpected warning(s): %r."
794+
% extra_warnings)

0 commit comments

Comments
 (0)