Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

option callback on value change, use an option to control use_inf_as_null #2504

Merged
merged 3 commits into from Dec 13, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion RELEASE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pandas 0.10.0
instead. This is a legacy hack and can lead to subtle bugs.
- inf/-inf are no longer considered as NA by isnull/notnull. To be clear, this
is legacy cruft from early pandas. This behavior can be globally re-enabled
using pandas.core.common.use_inf_as_na (#2050, #1919)
using the new option ``mode.use_inf_as_null`` (#2050, #1919)
- ``pandas.merge`` will now default to ``sort=False``. For many use cases
sorting the join keys is not necessary, and doing it by default is wasteful
- ``names`` handling in file parsing: if explicit column `names` passed,
Expand Down
2 changes: 1 addition & 1 deletion doc/source/missing_data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ arise and we wish to also consider that "missing" or "null".

Until recently, for legacy reasons ``inf`` and ``-inf`` were also
considered to be "null" in computations. This is no longer the case by
default; use the :func: `~pandas.core.common.use_inf_as_null` function to recover it.
default; use the ``mode.use_inf_as_null`` option to recover it.

.. _missing.isnull:

Expand Down
7 changes: 4 additions & 3 deletions pandas/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ def isnull_old(obj):
else:
return obj is None

def use_inf_as_null(flag):
'''
def _use_inf_as_null(key):
'''Option change callback for null/inf behaviour
Choose which replacement for numpy.isnan / -numpy.isfinite is used.

Parameters
Expand All @@ -113,6 +113,7 @@ def use_inf_as_null(flag):
* http://stackoverflow.com/questions/4859217/
programmatically-creating-variables-in-python/4859312#4859312
'''
flag = get_option(key)
if flag == True:
globals()['isnull'] = isnull_old
else:
Expand Down Expand Up @@ -1179,7 +1180,7 @@ def in_interactive_session():
returns True if running under python/ipython interactive shell
"""
import __main__ as main
return not hasattr(main, '__file__') or get_option('test.interactive')
return not hasattr(main, '__file__') or get_option('mode.sim_interactive')

def in_qtconsole():
"""
Expand Down
16 changes: 12 additions & 4 deletions pandas/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
- all options in a certain sub - namespace can be reset at once.
- the user can set / get / reset or ask for the description of an option.
- a developer can register and mark an option as deprecated.

- you can register a callback to be invoked when the the option value
is set or reset. Changing the stored value is considered misuse, but
is not verboten.

Implementation
==============
Expand Down Expand Up @@ -54,7 +56,7 @@
import warnings

DeprecatedOption = namedtuple('DeprecatedOption', 'key msg rkey removal_ver')
RegisteredOption = namedtuple('RegisteredOption', 'key defval doc validator')
RegisteredOption = namedtuple('RegisteredOption', 'key defval doc validator cb')

_deprecated_options = {} # holds deprecated option metdata
_registered_options = {} # holds registered option metdata
Expand Down Expand Up @@ -105,6 +107,9 @@ def _set_option(pat, value):
root, k = _get_root(key)
root[k] = value

if o and o.cb:
o.cb(key)


def _describe_option(pat='', _print_desc=True):

Expand Down Expand Up @@ -270,7 +275,7 @@ def __doc__(self):
######################################################
# Functions for use by pandas developers, in addition to User - api

def register_option(key, defval, doc='', validator=None):
def register_option(key, defval, doc='', validator=None, cb=None):
"""Register an option in the package-wide pandas config object

Parameters
Expand All @@ -280,6 +285,9 @@ def register_option(key, defval, doc='', validator=None):
doc - a string description of the option
validator - a function of a single argument, should raise `ValueError` if
called with a value which is not a legal value for the option.
cb - a function of a single argument "key", which is called
immediately after an option value is set/reset. key is
the full name of the option.

Returns
-------
Expand Down Expand Up @@ -321,7 +329,7 @@ def register_option(key, defval, doc='', validator=None):

# save the option metadata
_registered_options[key] = RegisteredOption(key=key, defval=defval,
doc=doc, validator=validator)
doc=doc, validator=validator,cb=cb)


def deprecate_option(key, msg=None, rkey=None, removal_ver=None):
Expand Down
23 changes: 20 additions & 3 deletions pandas/core/config_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,27 @@
cf.register_option('expand_frame_repr', True, pc_expand_repr_doc)
cf.register_option('line_width', 80, pc_line_width_doc)

tc_interactive_doc="""
tc_sim_interactive_doc="""
: boolean
Default False
Whether to simulate interactive mode for purposes of testing
"""
with cf.config_prefix('test'):
cf.register_option('interactive', False, tc_interactive_doc)
with cf.config_prefix('mode'):
cf.register_option('sim_interactive', False, tc_sim_interactive_doc)

use_inf_as_null_doc="""
: boolean
True means treat None, NaN, INF, -INF as null (old way),
False means None and NaN are null, but INF, -INF are not null
(new way).
"""

# we don't want to start importing evrything at the global context level
# or we'll hit circular deps.
def use_inf_as_null_cb(key):
from pandas.core.common import _use_inf_as_null
_use_inf_as_null(key)

with cf.config_prefix('mode'):
cf.register_option('use_inf_as_null', False, use_inf_as_null_doc,
cb=use_inf_as_null_cb)
9 changes: 5 additions & 4 deletions pandas/tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import unittest

from pandas import Series, DataFrame, date_range, DatetimeIndex
from pandas.core.common import notnull, isnull, use_inf_as_null
from pandas.core.common import notnull, isnull
import pandas.core.common as com
import pandas.util.testing as tm
import pandas.core.config as cf

import numpy as np

Expand All @@ -29,15 +30,15 @@ def test_notnull():
assert not notnull(None)
assert not notnull(np.NaN)

use_inf_as_null(False)
cf.set_option("mode.use_inf_as_null",False)
assert notnull(np.inf)
assert notnull(-np.inf)

use_inf_as_null(True)
cf.set_option("mode.use_inf_as_null",True)
assert not notnull(np.inf)
assert not notnull(-np.inf)

use_inf_as_null(False)
cf.set_option("mode.use_inf_as_null",False)

float_series = Series(np.random.randn(5))
obj_series = Series(np.random.randn(5), dtype=object)
Expand Down
25 changes: 25 additions & 0 deletions pandas/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,31 @@ def test_config_prefix(self):
self.assertEqual(self.cf.get_option('a'), 1)
self.assertEqual(self.cf.get_option('b'), 2)

def test_callback(self):
k=[None]
v=[None]
def callback(key):
k.append(key)
v.append(self.cf.get_option(key))

self.cf.register_option('d.a', 'foo',cb=callback)
self.cf.register_option('d.b', 'foo',cb=callback)

del k[-1],v[-1]
self.cf.set_option("d.a","fooz")
self.assertEqual(k[-1],"d.a")
self.assertEqual(v[-1],"fooz")

del k[-1],v[-1]
self.cf.set_option("d.b","boo")
self.assertEqual(k[-1],"d.b")
self.assertEqual(v[-1],"boo")

del k[-1],v[-1]
self.cf.reset_option("d.b")
self.assertEqual(k[-1],"d.b")



# fmt.reset_printoptions and fmt.set_printoptions were altered
# to use core.config, test_format exercises those paths.
32 changes: 16 additions & 16 deletions pandas/tests/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ def test_repr_should_return_str (self):
self.assertTrue(type(df.__repr__() == str)) # both py2 / 3

def test_repr_no_backslash(self):
pd.set_option('test.interactive', True)
pd.set_option('mode.sim_interactive', True)
df = DataFrame(np.random.randn(10, 4))

self.assertTrue('\\' not in repr(df))
pd.reset_option('test.interactive')
pd.reset_option('mode.sim_interactive')

def test_to_string_repr_unicode(self):
buf = StringIO()
Expand Down Expand Up @@ -409,7 +409,7 @@ def test_frame_info_encoding(self):
fmt.set_printoptions(max_rows=200)

def test_wide_repr(self):
set_option('test.interactive', True)
set_option('mode.sim_interactive', True)
col = lambda l, k: [tm.rands(k) for _ in xrange(l)]
df = DataFrame([col(20, 25) for _ in range(10)])
set_option('print.expand_frame_repr', False)
Expand All @@ -423,19 +423,19 @@ def test_wide_repr(self):
self.assert_(len(wider_repr) < len(wide_repr))

reset_option('print.expand_frame_repr')
set_option('test.interactive', False)
set_option('mode.sim_interactive', False)
set_option('print.line_width', 80)

def test_wide_repr_wide_columns(self):
set_option('test.interactive', True)
set_option('mode.sim_interactive', True)
df = DataFrame(randn(5, 3), columns=['a' * 90, 'b' * 90, 'c' * 90])
rep_str = repr(df)

self.assert_(len(rep_str.splitlines()) == 20)
reset_option('test.interactive')
reset_option('mode.sim_interactive')

def test_wide_repr_named(self):
set_option('test.interactive', True)
set_option('mode.sim_interactive', True)
col = lambda l, k: [tm.rands(k) for _ in xrange(l)]
df = DataFrame([col(20, 25) for _ in range(10)])
df.index.name = 'DataFrame Index'
Expand All @@ -454,11 +454,11 @@ def test_wide_repr_named(self):
self.assert_('DataFrame Index' in line)

reset_option('print.expand_frame_repr')
set_option('test.interactive', False)
set_option('mode.sim_interactive', False)
set_option('print.line_width', 80)

def test_wide_repr_multiindex(self):
set_option('test.interactive', True)
set_option('mode.sim_interactive', True)
col = lambda l, k: [tm.rands(k) for _ in xrange(l)]
midx = pandas.MultiIndex.from_arrays([np.array(col(10, 5)),
np.array(col(10, 5))])
Expand All @@ -479,11 +479,11 @@ def test_wide_repr_multiindex(self):
self.assert_('Level 0 Level 1' in line)

reset_option('print.expand_frame_repr')
set_option('test.interactive', False)
set_option('mode.sim_interactive', False)
set_option('print.line_width', 80)

def test_wide_repr_multiindex_cols(self):
set_option('test.interactive', True)
set_option('mode.sim_interactive', True)
col = lambda l, k: [tm.rands(k) for _ in xrange(l)]
midx = pandas.MultiIndex.from_arrays([np.array(col(10, 5)),
np.array(col(10, 5))])
Expand All @@ -505,11 +505,11 @@ def test_wide_repr_multiindex_cols(self):
self.assert_(len(wide_repr.splitlines()) == 14 * 10 - 1)

reset_option('print.expand_frame_repr')
set_option('test.interactive', False)
set_option('mode.sim_interactive', False)
set_option('print.line_width', 80)

def test_wide_repr_unicode(self):
set_option('test.interactive', True)
set_option('mode.sim_interactive', True)
col = lambda l, k: [tm.randu(k) for _ in xrange(l)]
df = DataFrame([col(20, 25) for _ in range(10)])
set_option('print.expand_frame_repr', False)
Expand All @@ -523,18 +523,18 @@ def test_wide_repr_unicode(self):
self.assert_(len(wider_repr) < len(wide_repr))

reset_option('print.expand_frame_repr')
set_option('test.interactive', False)
set_option('mode.sim_interactive', False)
set_option('print.line_width', 80)

def test_wide_repr_wide_long_columns(self):
set_option('test.interactive', True)
set_option('mode.sim_interactive', True)

df = DataFrame({'a': ['a'*30, 'b'*30], 'b': ['c'*70, 'd'*80]})

result = repr(df)
self.assertTrue('ccccc' in result)
self.assertTrue('ddddd' in result)
set_option('test.interactive', False)
set_option('mode.sim_interactive', False)

def test_to_string(self):
from pandas import read_table
Expand Down