Skip to content

Commit 429bc51

Browse files
committed
COMPAT: Expand compatibility with fromnumeric.py
Augment pandas array-like methods with appropriate parameters (generally, '*args' and '**kwargs') so that they can be called via analogous functions in the numpy library they are defined in 'fromnumeric.py'. Closes gh-12638. Closes gh-12644. Closes gh-12687.
1 parent 3ff5af0 commit 429bc51

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1818
-266
lines changed

doc/source/whatsnew/v0.18.1.txt

+34-1
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,39 @@ New Behavior:
357357
df.groupby('c', sort=False).nth(1)
358358

359359

360+
.. _whatsnew_0181.numpy_compatibility
361+
362+
Compatibility between pandas array-like methods (e.g. ```sum`` and ``take``) and their ``numpy``
363+
counterparts has been greatly increased by augmenting the signatures of the ``pandas`` methods so
364+
as to accept arguments that can be passed in from ``numpy``, even if they are not necessarily
365+
used in the ``pandas`` implementation (:issue:`12644`). Issues that were addressed were:
366+
367+
- ``.searchsorted()`` for ``Index`` and ``TimedeltaIndex`` now accept a ``sorter`` argument to maintain compatibility with numpy's ``searchsorted`` function (:issue:`12238`)
368+
- Bug in numpy compatibility of ``np.round()`` on a ``Series`` (:issue:`12600`)
369+
370+
An example of this signature augmentation is illustrated below:
371+
372+
Previous behaviour:
373+
374+
.. code-block:: ipython
375+
376+
In [1]: sp = pd.SparseDataFrame([1, 2, 3])
377+
In [2]: np.cumsum(sp, axis=0)
378+
...
379+
TypeError: cumsum() takes at most 2 arguments (4 given)
380+
381+
New behaviour:
382+
383+
.. code-block:: ipython
384+
385+
In [1]: sp = pd.SparseDataFrame([1, 2, 3])
386+
In [2]: np.cumsum(sp, axis=0)
387+
Out[1]:
388+
0
389+
0 1.0
390+
1 3.0
391+
2 6.0
392+
360393
.. _whatsnew_0181.apply_resample:
361394

362395
Using ``.apply`` on groupby resampling
@@ -527,7 +560,6 @@ Bug Fixes
527560
- Bug in ``.resample(...)`` with a ``PeriodIndex`` casting to a ``DatetimeIndex`` when empty (:issue:`12868`)
528561
- Bug in ``.resample(...)`` with a ``PeriodIndex`` when resampling to an existing frequency (:issue:`12770`)
529562
- Bug in printing data which contains ``Period`` with different ``freq`` raises ``ValueError`` (:issue:`12615`)
530-
- Bug in numpy compatibility of ``np.round()`` on a ``Series`` (:issue:`12600`)
531563
- Bug in ``Series`` construction with ``Categorical`` and ``dtype='category'`` is specified (:issue:`12574`)
532564
- Bugs in concatenation with a coercable dtype was too aggressive, resulting in different dtypes in outputformatting when an object was longer than ``display.max_rows`` (:issue:`12411`, :issue:`12045`, :issue:`11594`, :issue:`10571`, :issue:`12211`)
533565
- Bug in ``float_format`` option with option not being validated as a callable. (:issue:`12706`)
@@ -547,6 +579,7 @@ Bug Fixes
547579
- Segfault in ``to_json`` when attempting to serialise a ``DataFrame`` or ``Series`` with non-ndarray values (:issue:`10778`).
548580
- Bug in ``.align`` not returning the sub-class (:issue:`12983`)
549581
- Bug in aligning a ``Series`` with a ``DataFrame`` (:issue:`13037`)
582+
- Bug in ``ABCPanel`` in which ``Panel4D`` was not being considered as a valid instance of this generic type (:issue:`12810`)
550583

551584

552585
- Bug in consistency of ``.name`` on ``.groupby(..).apply(..)`` cases (:issue:`12363`)

pandas/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020

2121
# numpy compat
22-
from pandas.compat.numpy_compat import *
22+
from pandas.compat.numpy import *
2323

2424
try:
2525
from pandas import hashtable, tslib, lib
File renamed without changes.

pandas/compat/numpy/function.py

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
"""
2+
For compatibility with numpy libraries, pandas functions or
3+
methods have to accept '*args' and '**kwargs' parameters to
4+
accommodate numpy arguments that are not actually used or
5+
respected in the pandas implementation.
6+
7+
To ensure that users do not abuse these parameters, validation
8+
is performed in 'validators.py' to make sure that any extra
9+
parameters passed correspond ONLY to those in the numpy signature.
10+
Part of that validation includes whether or not the user attempted
11+
to pass in non-default values for these extraneous parameters. As we
12+
want to discourage users from relying on these parameters when calling
13+
the pandas implementation, we want them only to pass in the default values
14+
for these parameters.
15+
16+
This module provides a set of commonly used default arguments for functions
17+
and methods that are spread throughout the codebase. This module will make it
18+
easier to adjust to future upstream changes in the analogous numpy signatures.
19+
"""
20+
21+
from numpy import ndarray
22+
from pandas.util.validators import (validate_args, validate_kwargs,
23+
validate_args_and_kwargs)
24+
from pandas.core.common import is_integer
25+
from pandas.compat import OrderedDict
26+
27+
28+
class CompatValidator(object):
29+
def __init__(self, defaults, fname=None, method=None,
30+
max_fname_arg_count=None):
31+
self.fname = fname
32+
self.method = method
33+
self.defaults = defaults
34+
self.max_fname_arg_count = max_fname_arg_count
35+
36+
def __call__(self, args, kwargs, fname=None,
37+
max_fname_arg_count=None, method=None):
38+
fname = self.fname if fname is None else fname
39+
max_fname_arg_count = (self.max_fname_arg_count if
40+
max_fname_arg_count is None
41+
else max_fname_arg_count)
42+
method = self.method if method is None else method
43+
44+
if method == 'args':
45+
validate_args(fname, args, max_fname_arg_count, self.defaults)
46+
elif method == 'kwargs':
47+
validate_kwargs(fname, kwargs, self.defaults)
48+
elif method == 'both':
49+
validate_args_and_kwargs(fname, args, kwargs,
50+
max_fname_arg_count,
51+
self.defaults)
52+
else:
53+
raise ValueError("invalid validation method "
54+
"'{method}'".format(method=method))
55+
56+
ARGMINMAX_DEFAULTS = dict(out=None)
57+
validate_argmin = CompatValidator(ARGMINMAX_DEFAULTS, fname='argmin',
58+
method='both', max_fname_arg_count=1)
59+
validate_argmax = CompatValidator(ARGMINMAX_DEFAULTS, fname='argmax',
60+
method='both', max_fname_arg_count=1)
61+
62+
63+
def process_skipna(skipna, args):
64+
if isinstance(skipna, ndarray) or skipna is None:
65+
args = (skipna,) + args
66+
skipna = True
67+
68+
return skipna, args
69+
70+
71+
def validate_argmin_with_skipna(skipna, args, kwargs):
72+
"""
73+
If 'Series.argmin' is called via the 'numpy' library,
74+
the third parameter in its signature is 'out', which
75+
takes either an ndarray or 'None', so check if the
76+
'skipna' parameter is either an instance of ndarray or
77+
is None, since 'skipna' itself should be a boolean
78+
"""
79+
80+
skipna, args = process_skipna(skipna, args)
81+
validate_argmin(args, kwargs)
82+
return skipna
83+
84+
85+
def validate_argmax_with_skipna(skipna, args, kwargs):
86+
"""
87+
If 'Series.argmax' is called via the 'numpy' library,
88+
the third parameter in its signature is 'out', which
89+
takes either an ndarray or 'None', so check if the
90+
'skipna' parameter is either an instance of ndarray or
91+
is None, since 'skipna' itself should be a boolean
92+
"""
93+
94+
skipna, args = process_skipna(skipna, args)
95+
validate_argmax(args, kwargs)
96+
return skipna
97+
98+
ARGSORT_DEFAULTS = OrderedDict()
99+
ARGSORT_DEFAULTS['axis'] = -1
100+
ARGSORT_DEFAULTS['kind'] = 'quicksort'
101+
ARGSORT_DEFAULTS['order'] = None
102+
validate_argsort = CompatValidator(ARGSORT_DEFAULTS, fname='argsort',
103+
max_fname_arg_count=0, method='both')
104+
105+
106+
def validate_argsort_with_ascending(ascending, args, kwargs):
107+
"""
108+
If 'Categorical.argsort' is called via the 'numpy' library, the
109+
first parameter in its signature is 'axis', which takes either
110+
an integer or 'None', so check if the 'ascending' parameter has
111+
either integer type or is None, since 'ascending' itself should
112+
be a boolean
113+
"""
114+
115+
if is_integer(ascending) or ascending is None:
116+
args = (ascending,) + args
117+
ascending = True
118+
119+
validate_argsort(args, kwargs, max_fname_arg_count=1)
120+
return ascending
121+
122+
CLIP_DEFAULTS = dict(out=None)
123+
validate_clip = CompatValidator(CLIP_DEFAULTS, fname='clip',
124+
method='both', max_fname_arg_count=3)
125+
126+
127+
def validate_clip_with_axis(axis, args, kwargs):
128+
"""
129+
If 'NDFrame.clip' is called via the numpy library, the third
130+
parameter in its signature is 'out', which can takes an ndarray,
131+
so check if the 'axis' parameter is an instance of ndarray, since
132+
'axis' itself should either be an integer or None
133+
"""
134+
135+
if isinstance(axis, ndarray):
136+
args = (axis,) + args
137+
axis = None
138+
139+
validate_clip(args, kwargs)
140+
return axis
141+
142+
COMPRESS_DEFAULTS = OrderedDict()
143+
COMPRESS_DEFAULTS['axis'] = None
144+
COMPRESS_DEFAULTS['out'] = None
145+
validate_compress = CompatValidator(COMPRESS_DEFAULTS, fname='compress',
146+
method='both', max_fname_arg_count=1)
147+
148+
CUM_FUNC_DEFAULTS = OrderedDict()
149+
CUM_FUNC_DEFAULTS['dtype'] = None
150+
CUM_FUNC_DEFAULTS['out'] = None
151+
validate_cum_func = CompatValidator(CUM_FUNC_DEFAULTS, method='kwargs')
152+
validate_cumsum = CompatValidator(CUM_FUNC_DEFAULTS, fname='cumsum',
153+
method='both', max_fname_arg_count=1)
154+
155+
LOGICAL_FUNC_DEFAULTS = dict(out=None)
156+
validate_logical_func = CompatValidator(LOGICAL_FUNC_DEFAULTS, method='kwargs')
157+
158+
MINMAX_DEFAULTS = dict(out=None)
159+
validate_min = CompatValidator(MINMAX_DEFAULTS, fname='min',
160+
method='both', max_fname_arg_count=1)
161+
validate_max = CompatValidator(MINMAX_DEFAULTS, fname='max',
162+
method='both', max_fname_arg_count=1)
163+
164+
RESHAPE_DEFAULTS = dict(order='C')
165+
validate_reshape = CompatValidator(RESHAPE_DEFAULTS, fname='reshape',
166+
method='both', max_fname_arg_count=1)
167+
168+
REPEAT_DEFAULTS = dict(axis=None)
169+
validate_repeat = CompatValidator(REPEAT_DEFAULTS, fname='repeat',
170+
method='both', max_fname_arg_count=1)
171+
172+
ROUND_DEFAULTS = dict(out=None)
173+
validate_round = CompatValidator(ROUND_DEFAULTS, fname='round',
174+
method='both', max_fname_arg_count=1)
175+
176+
SORT_DEFAULTS = OrderedDict()
177+
SORT_DEFAULTS['axis'] = -1
178+
SORT_DEFAULTS['kind'] = 'quicksort'
179+
SORT_DEFAULTS['order'] = None
180+
validate_sort = CompatValidator(SORT_DEFAULTS, fname='sort',
181+
method='kwargs')
182+
183+
STAT_FUNC_DEFAULTS = OrderedDict()
184+
STAT_FUNC_DEFAULTS['dtype'] = None
185+
STAT_FUNC_DEFAULTS['out'] = None
186+
validate_stat_func = CompatValidator(STAT_FUNC_DEFAULTS,
187+
method='kwargs')
188+
validate_sum = CompatValidator(STAT_FUNC_DEFAULTS, fname='sort',
189+
method='both', max_fname_arg_count=1)
190+
validate_mean = CompatValidator(STAT_FUNC_DEFAULTS, fname='mean',
191+
method='both', max_fname_arg_count=1)
192+
193+
STAT_DDOF_FUNC_DEFAULTS = OrderedDict()
194+
STAT_DDOF_FUNC_DEFAULTS['dtype'] = None
195+
STAT_DDOF_FUNC_DEFAULTS['out'] = None
196+
validate_stat_ddof_func = CompatValidator(STAT_DDOF_FUNC_DEFAULTS,
197+
method='kwargs')
198+
199+
# Currently, numpy (v1.11) has backwards compatibility checks
200+
# in place so that this 'kwargs' parameter is technically
201+
# unnecessary, but in the long-run, this will be needed.
202+
SQUEEZE_DEFAULTS = dict(axis=None)
203+
validate_squeeze = CompatValidator(SQUEEZE_DEFAULTS, fname='squeeze',
204+
method='kwargs')
205+
206+
TAKE_DEFAULTS = OrderedDict()
207+
TAKE_DEFAULTS['out'] = None
208+
TAKE_DEFAULTS['mode'] = 'raise'
209+
validate_take = CompatValidator(TAKE_DEFAULTS, fname='take',
210+
method='kwargs')
211+
212+
213+
def validate_take_with_convert(convert, args, kwargs):
214+
"""
215+
If this function is called via the 'numpy' library, the third
216+
parameter in its signature is 'axis', which takes either an
217+
ndarray or 'None', so check if the 'convert' parameter is either
218+
an instance of ndarray or is None
219+
"""
220+
221+
if isinstance(convert, ndarray) or convert is None:
222+
args = (convert,) + args
223+
convert = True
224+
225+
validate_take(args, kwargs, max_fname_arg_count=3, method='both')
226+
return convert
227+
228+
TRANSPOSE_DEFAULTS = dict(axes=None)
229+
validate_transpose = CompatValidator(TRANSPOSE_DEFAULTS, fname='transpose',
230+
method='both', max_fname_arg_count=0)
231+
232+
233+
def validate_transpose_for_generic(inst, kwargs):
234+
try:
235+
validate_transpose(tuple(), kwargs)
236+
except ValueError as e:
237+
klass = type(inst).__name__
238+
msg = str(e)
239+
240+
# the Panel class actual relies on the 'axes' parameter if called
241+
# via the 'numpy' library, so let's make sure the error is specific
242+
# about saying that the parameter is not supported for particular
243+
# implementations of 'transpose'
244+
if "the 'axes' parameter is not supported" in msg:
245+
msg += " for {klass} instances".format(klass=klass)
246+
247+
raise ValueError(msg)

pandas/compat/pickle_compat.py

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# flake8: noqa
44

55
import sys
6-
import numpy as np
76
import pandas
87
import copy
98
import pickle as pkl

pandas/core/base.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pandas.core import common as com
88
import pandas.core.nanops as nanops
99
import pandas.lib as lib
10+
from pandas.compat.numpy import function as nv
1011
from pandas.util.decorators import (Appender, cache_readonly,
1112
deprecate_kwarg, Substitution)
1213
from pandas.core.common import AbstractMethodError
@@ -798,8 +799,9 @@ class IndexOpsMixin(object):
798799
# ndarray compatibility
799800
__array_priority__ = 1000
800801

801-
def transpose(self):
802+
def transpose(self, *args, **kwargs):
802803
""" return the transpose, which is by definition self """
804+
nv.validate_transpose(args, kwargs)
803805
return self
804806

805807
T = property(transpose, doc="return the transpose, which is by "

0 commit comments

Comments
 (0)