diff options
-rw-r--r-- | doc/release/1.13.0-notes.rst | 16 | ||||
-rw-r--r-- | doc/source/reference/routines.set.rst | 1 | ||||
-rw-r--r-- | numpy/add_newdocs.py | 2 | ||||
-rw-r--r-- | numpy/core/arrayprint.py | 11 | ||||
-rw-r--r-- | numpy/core/include/numpy/ndarraytypes.h | 15 | ||||
-rw-r--r-- | numpy/core/src/multiarray/arraytypes.c.src | 12 | ||||
-rw-r--r-- | numpy/core/src/multiarray/multiarray_tests.c.src | 11 | ||||
-rw-r--r-- | numpy/core/tests/test_arrayprint.py | 8 | ||||
-rw-r--r-- | numpy/core/tests/test_deprecations.py | 8 | ||||
-rw-r--r-- | numpy/distutils/ccompiler.py | 22 | ||||
-rw-r--r-- | numpy/distutils/misc_util.py | 12 | ||||
-rw-r--r-- | numpy/distutils/unixccompiler.py | 5 | ||||
-rw-r--r-- | numpy/f2py/capi_maps.py | 9 | ||||
-rw-r--r-- | numpy/f2py/cfuncs.py | 3 | ||||
-rw-r--r-- | numpy/f2py/src/fortranobject.c | 28 | ||||
-rw-r--r-- | numpy/f2py/tests/src/string/char.f90 | 29 | ||||
-rw-r--r-- | numpy/f2py/tests/test_string.py | 26 | ||||
-rw-r--r-- | numpy/lib/arraysetops.py | 102 | ||||
-rw-r--r-- | numpy/lib/info.py | 8 | ||||
-rw-r--r-- | numpy/lib/tests/test_arraysetops.py | 42 | ||||
-rw-r--r-- | numpy/ma/extras.py | 29 | ||||
-rw-r--r-- | numpy/ma/tests/test_extras.py | 23 |
22 files changed, 395 insertions, 27 deletions
diff --git a/doc/release/1.13.0-notes.rst b/doc/release/1.13.0-notes.rst index f594c1825..6be1afe64 100644 --- a/doc/release/1.13.0-notes.rst +++ b/doc/release/1.13.0-notes.rst @@ -25,6 +25,9 @@ Deprecations * Calling ``np.fix``, ``np.isposinf``, and ``np.isneginf`` with ``f(x, y=out)`` is deprecated - the argument should be passed as ``f(x, out=out)``, which matches other ufunc-like interfaces. +* Use of the C-API ``NPY_CHAR`` type number deprecated since version 1.7 will + now raise deprecation warnings at runtime. Extensions built with older f2py + versions need to be recompiled to remove the warning. Build System Changes @@ -154,6 +157,12 @@ In an N-dimensional array, the user can now choose the axis along which to look for duplicate N-1-dimensional elements using ``numpy.unique``. The original behaviour is recovered if ``axis=None`` (default). +``isin`` function, improving on ``in1d`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The new function ``isin`` tests whether each element of an N-dimensonal +array is present anywhere within a second array. It is an enhancement +of ``in1d`` that preserves the shape of the first array. + ``np.gradient`` now supports unevenly spaced data ------------------------------------------------- Users can now specify a not-constant spacing for data. @@ -332,6 +341,13 @@ New ``positive`` ufunc This ufunc corresponds to unary `+`, but unlike `+` on an ndarray it will raise an error if array values do not support numeric operations. +Better ``repr`` of object arrays +-------------------------------- +Object arrays that contain themselves no longer cause a recursion error. + +Object arrays that contain ``list`` objects are now printed in a way that makes +clear the difference between a 2d object array, and a 1d object array of lists. + Changes ======= diff --git a/doc/source/reference/routines.set.rst b/doc/source/reference/routines.set.rst index 27c6aeb89..0089fb3e9 100644 --- a/doc/source/reference/routines.set.rst +++ b/doc/source/reference/routines.set.rst @@ -17,6 +17,7 @@ Boolean operations in1d intersect1d + isin setdiff1d setxor1d union1d diff --git a/numpy/add_newdocs.py b/numpy/add_newdocs.py index 6e859bd90..df79ae136 100644 --- a/numpy/add_newdocs.py +++ b/numpy/add_newdocs.py @@ -1500,7 +1500,7 @@ add_newdoc('numpy.core.multiarray', 'where', Find the indices of elements of `x` that are in `goodvalues`. >>> goodvalues = [3, 4, 7] - >>> ix = np.in1d(x.ravel(), goodvalues).reshape(x.shape) + >>> ix = np.isin(x, goodvalues) >>> ix array([[False, False, False], [ True, True, False], diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index dba9dffb3..e54f4602a 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -102,6 +102,7 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None, - 'complexfloat' - 'longcomplexfloat' : composed of two 128-bit floats - 'numpystr' : types `numpy.string_` and `numpy.unicode_` + - 'object' : `np.object_` arrays - 'str' : all other strings Other keys that can be used to set a group of types at once are:: @@ -241,6 +242,13 @@ def _boolFormatter(x): else: return 'False' +def _object_format(o): + """ Object arrays containing lists should be printed unambiguously """ + if type(o) is list: + fmt = 'list({!r})' + else: + fmt = '{!r}' + return fmt.format(o) def repr_format(x): return repr(x) @@ -256,6 +264,7 @@ def _get_formatdict(data, precision, suppress_small, formatter): 'longcomplexfloat': lambda: LongComplexFormat(precision), 'datetime': lambda: DatetimeFormat(data), 'timedelta': lambda: TimedeltaFormat(data), + 'object': lambda: _object_format, 'numpystr': lambda: repr_format, 'str': lambda: str} @@ -326,6 +335,8 @@ def _get_format_function(data, precision, suppress_small, formatter): return formatdict['numpystr']() elif issubclass(dtypeobj, _nt.datetime64): return formatdict['datetime']() + elif issubclass(dtypeobj, _nt.object_): + return formatdict['object']() else: return formatdict['numpystr']() diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index 7f6fe6524..e0df189f9 100644 --- a/numpy/core/include/numpy/ndarraytypes.h +++ b/numpy/core/include/numpy/ndarraytypes.h @@ -15,7 +15,17 @@ #define NPY_ALLOW_THREADS 0 #endif +#ifndef __has_extension +#define __has_extension(x) 0 +#endif +#if !defined(_NPY_NO_DEPRECATIONS) && \ + ((defined(__GNUC__)&& __GNUC__ >= 6) || \ + __has_extension(attribute_deprecated_with_message)) +#define NPY_ATTR_DEPRECATE(text) __attribute__ ((deprecated (text))) +#else +#define NPY_ATTR_DEPRECATE(text) +#endif /* * There are several places in the code where an array of dimensions @@ -71,12 +81,15 @@ enum NPY_TYPES { NPY_BOOL=0, NPY_NTYPES, NPY_NOTYPE, - NPY_CHAR, /* special flag */ + NPY_CHAR NPY_ATTR_DEPRECATE("Use NPY_STRING"), NPY_USERDEF=256, /* leave room for characters */ /* The number of types not including the new 1.6 types */ NPY_NTYPES_ABI_COMPATIBLE=21 }; +#ifdef _MSC_VER +#pragma deprecated(NPY_CHAR) +#endif /* basetype array priority */ #define NPY_PRIORITY 0.0 diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index 49d6ae1d2..b11134305 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -6,6 +6,7 @@ #define NPY_NO_DEPRECATED_API NPY_API_VERSION #define _MULTIARRAYMODULE +#define _NPY_NO_DEPRECATIONS /* for NPY_CHAR */ #include "numpy/npy_common.h" #include "numpy/arrayobject.h" @@ -4415,6 +4416,17 @@ PyArray_DescrFromType(int type) return NULL; } else if ((type == NPY_CHAR) || (type == NPY_CHARLTR)) { + if (type == NPY_CHAR) { + /* + * warning added 2017-04-25, 1.13 + * deprecated in 1.7 + * */ + if (DEPRECATE("The NPY_CHAR type_num is deprecated. " + "Please port your code to use " + "NPY_STRING instead.") < 0) { + return NULL; + } + } ret = PyArray_DescrNew(_builtin_descrs[NPY_STRING]); if (ret == NULL) { return NULL; diff --git a/numpy/core/src/multiarray/multiarray_tests.c.src b/numpy/core/src/multiarray/multiarray_tests.c.src index 45092dc0c..de05cc280 100644 --- a/numpy/core/src/multiarray/multiarray_tests.c.src +++ b/numpy/core/src/multiarray/multiarray_tests.c.src @@ -1,6 +1,7 @@ /* -*-c-*- */ #define NPY_NO_DEPRECATED_API NPY_API_VERSION #include <Python.h> +#define _NPY_NO_DEPRECATIONS /* for NPY_CHAR */ #include "numpy/arrayobject.h" #include "mem_overlap.h" #include "npy_extint128.h" @@ -608,6 +609,13 @@ incref_elide_l(PyObject *dummy, PyObject *args) return res; } +/* used to test NPY_CHAR usage emits deprecation warning */ +static PyObject* +npy_char_deprecation(PyObject* NPY_UNUSED(self), PyObject* NPY_UNUSED(args)) +{ + PyArray_Descr * descr = PyArray_DescrFromType(NPY_CHAR); + return (PyObject *)descr; +} #if !defined(NPY_PY3K) static PyObject * @@ -1576,6 +1584,9 @@ static PyMethodDef Multiarray_TestsMethods[] = { {"incref_elide_l", incref_elide_l, METH_VARARGS, NULL}, + {"npy_char_deprecation", + npy_char_deprecation, + METH_NOARGS, NULL}, #if !defined(NPY_PY3K) {"test_int_subclass", int_subclass, diff --git a/numpy/core/tests/test_arrayprint.py b/numpy/core/tests/test_arrayprint.py index b228527da..e7ac0cdfd 100644 --- a/numpy/core/tests/test_arrayprint.py +++ b/numpy/core/tests/test_arrayprint.py @@ -52,6 +52,14 @@ class TestArrayRepr(object): assert_equal(repr(first), 'array(array(array(..., dtype=object), dtype=object), dtype=object)') + def test_containing_list(self): + # printing square brackets directly would be ambiguuous + arr1d = np.array([None, None]) + arr1d[0] = [1, 2] + arr1d[1] = [3] + assert_equal(repr(arr1d), + 'array([list([1, 2]), list([3])], dtype=object)') + class TestComplexArray(TestCase): def test_str(self): diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 46b2c79aa..8e35f7e54 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -421,5 +421,13 @@ class TestClassicIntDivision(_DeprecationTestCase): dt2 = dt1 +class TestNPY_CHAR(_DeprecationTestCase): + # 2017-05-03, 1.13.0 + def test_npy_char_deprecation(self): + from numpy.core.multiarray_tests import npy_char_deprecation + self.assert_deprecated(npy_char_deprecation) + assert_(npy_char_deprecation() == 'S1') + + if __name__ == "__main__": run_module_suite() diff --git a/numpy/distutils/ccompiler.py b/numpy/distutils/ccompiler.py index 5f9bd4a84..98c4cc022 100644 --- a/numpy/distutils/ccompiler.py +++ b/numpy/distutils/ccompiler.py @@ -17,10 +17,11 @@ from numpy.distutils import log from numpy.distutils.compat import get_exception from numpy.distutils.exec_command import exec_command from numpy.distutils.misc_util import cyg2win32, is_sequence, mingw32, \ - quote_args, get_num_build_jobs + quote_args, get_num_build_jobs, \ + _commandline_dep_string -def _needs_build(obj): +def _needs_build(obj, cc_args, extra_postargs, pp_opts): """ Check if an objects needs to be rebuild based on its dependencies @@ -40,9 +41,20 @@ def _needs_build(obj): # dep_file is a makefile containing 'object: dependencies' # formated like posix shell (spaces escaped, \ line continuations) + # the last line contains the compiler commandline arguments as some + # projects may compile an extension multiple times with different + # arguments with open(dep_file, "r") as f: - deps = [x for x in shlex.split(f.read(), posix=True) - if x != "\n" and not x.endswith(":")] + lines = f.readlines() + + cmdline =_commandline_dep_string(cc_args, extra_postargs, pp_opts) + last_cmdline = lines[-1] + if last_cmdline != cmdline: + return True + + contents = ''.join(lines[:-1]) + deps = [x for x in shlex.split(contents, posix=True) + if x != "\n" and not x.endswith(":")] try: t_obj = os.stat(obj).st_mtime @@ -230,7 +242,7 @@ def CCompiler_compile(self, sources, output_dir=None, macros=None, def single_compile(args): obj, (src, ext) = args - if _needs_build(obj): + if _needs_build(obj, cc_args, extra_postargs, pp_opts): self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) if isinstance(self, FCompiler): diff --git a/numpy/distutils/misc_util.py b/numpy/distutils/misc_util.py index de0e4a47a..21aaece70 100644 --- a/numpy/distutils/misc_util.py +++ b/numpy/distutils/misc_util.py @@ -541,6 +541,18 @@ def _get_directories(list_of_sources): direcs.append(d[0]) return direcs +def _commandline_dep_string(cc_args, extra_postargs, pp_opts): + """ + Return commandline representation used to determine if a file needs + to be recompiled + """ + cmdline = 'commandline: ' + cmdline += ' '.join(cc_args) + cmdline += ' '.join(extra_postargs) + cmdline += ' '.join(pp_opts) + '\n' + return cmdline + + def get_dependencies(sources): #XXX scan sources for include statements return _get_headers(_get_directories(sources)) diff --git a/numpy/distutils/unixccompiler.py b/numpy/distutils/unixccompiler.py index 307b56ce4..6ed5eec6f 100644 --- a/numpy/distutils/unixccompiler.py +++ b/numpy/distutils/unixccompiler.py @@ -10,6 +10,7 @@ from distutils.errors import DistutilsExecError, CompileError from distutils.unixccompiler import * from numpy.distutils.ccompiler import replace_method from numpy.distutils.compat import get_exception +from numpy.distutils.misc_util import _commandline_dep_string if sys.version_info[0] < 3: from . import log @@ -59,6 +60,10 @@ def UnixCCompiler__compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts msg = str(get_exception()) raise CompileError(msg) + # add commandline flags to dependency file + with open(obj + '.d', 'a') as f: + f.write(_commandline_dep_string(cc_args, extra_postargs, pp_opts)) + replace_method(UnixCCompiler, '_compile', UnixCCompiler__compile) diff --git a/numpy/f2py/capi_maps.py b/numpy/f2py/capi_maps.py index 441629faa..5b2e6a9b9 100644 --- a/numpy/f2py/capi_maps.py +++ b/numpy/f2py/capi_maps.py @@ -65,7 +65,7 @@ c2py_map = {'double': 'float', c2capi_map = {'double': 'NPY_DOUBLE', 'float': 'NPY_FLOAT', 'long_double': 'NPY_DOUBLE', # forced casting - 'char': 'NPY_CHAR', + 'char': 'NPY_STRING', 'unsigned_char': 'NPY_UBYTE', 'signed_char': 'NPY_BYTE', 'short': 'NPY_SHORT', @@ -77,7 +77,7 @@ c2capi_map = {'double': 'NPY_DOUBLE', 'complex_float': 'NPY_CFLOAT', 'complex_double': 'NPY_CDOUBLE', 'complex_long_double': 'NPY_CDOUBLE', # forced casting - 'string': 'NPY_CHAR'} + 'string': 'NPY_STRING'} # These new maps aren't used anyhere yet, but should be by default # unless building numeric or numarray extensions. @@ -99,10 +99,7 @@ if using_newcore: 'complex_float': 'NPY_CFLOAT', 'complex_double': 'NPY_CDOUBLE', 'complex_long_double': 'NPY_CDOUBLE', - # f2py 2e is not ready for NPY_STRING (must set itemisize - # etc) - 'string': 'NPY_CHAR', - #'string':'NPY_STRING' + 'string':'NPY_STRING' } c2pycode_map = {'double': 'd', diff --git a/numpy/f2py/cfuncs.py b/numpy/f2py/cfuncs.py index 0d0a52764..1632a0d47 100644 --- a/numpy/f2py/cfuncs.py +++ b/numpy/f2py/cfuncs.py @@ -1153,8 +1153,9 @@ def buildcfuncs(): m] = '#define %s(v) (PyArray_SimpleNewFromData(0,NULL,%s,(char *)v))' % (m, c2capi_map[k]) k = 'string' m = 'pyarr_from_p_%s1' % k + # NPY_CHAR compatibility, NPY_STRING with itemsize 1 cppmacros[ - m] = '#define %s(v,dims) (PyArray_SimpleNewFromData(1,dims,NPY_CHAR,(char *)v))' % (m) + m] = '#define %s(v,dims) (PyArray_New(&PyArray_Type, 1, dims, NPY_STRING, NULL, v, 1, NPY_ARRAY_CARRAY, NULL))' % (m) ############ Auxiliary functions for sorting needs ################### diff --git a/numpy/f2py/src/fortranobject.c b/numpy/f2py/src/fortranobject.c index 9024dd5b3..0209cb1be 100644 --- a/numpy/f2py/src/fortranobject.c +++ b/numpy/f2py/src/fortranobject.c @@ -691,7 +691,7 @@ PyArrayObject* array_from_pyobj(const int type_num, } arr = (PyArrayObject *) PyArray_New(&PyArray_Type, rank, dims, type_num, - NULL,NULL,0, + NULL,NULL,1, !(intent&F2PY_INTENT_C), NULL); if (arr==NULL) return NULL; @@ -701,6 +701,15 @@ PyArrayObject* array_from_pyobj(const int type_num, } descr = PyArray_DescrFromType(type_num); + /* compatibility with NPY_CHAR */ + if (type_num == NPY_STRING) { + PyArray_DESCR_REPLACE(descr); + if (descr == NULL) { + return NULL; + } + descr->elsize = 1; + descr->type = NPY_CHARLTR; + } elsize = descr->elsize; typechar = descr->type; Py_DECREF(descr); @@ -781,9 +790,10 @@ PyArrayObject* array_from_pyobj(const int type_num, /* here we have always intent(in) or intent(inplace) */ { - PyArrayObject *retarr = (PyArrayObject *) \ + PyArrayObject * retarr; + retarr = (PyArrayObject *) \ PyArray_New(&PyArray_Type, PyArray_NDIM(arr), PyArray_DIMS(arr), type_num, - NULL,NULL,0, + NULL,NULL,1, !(intent&F2PY_INTENT_C), NULL); if (retarr==NULL) @@ -816,9 +826,19 @@ PyArrayObject* array_from_pyobj(const int type_num, } { + PyArray_Descr * descr = PyArray_DescrFromType(type_num); + /* compatibility with NPY_CHAR */ + if (type_num == NPY_STRING) { + PyArray_DESCR_REPLACE(descr); + if (descr == NULL) { + return NULL; + } + descr->elsize = 1; + descr->type = NPY_CHARLTR; + } F2PY_REPORT_ON_ARRAY_COPY_FROMANY; arr = (PyArrayObject *) \ - PyArray_FromAny(obj,PyArray_DescrFromType(type_num), 0,0, + PyArray_FromAny(obj, descr, 0,0, ((intent & F2PY_INTENT_C)?NPY_ARRAY_CARRAY:NPY_ARRAY_FARRAY) \ | NPY_ARRAY_FORCECAST, NULL); if (arr==NULL) diff --git a/numpy/f2py/tests/src/string/char.f90 b/numpy/f2py/tests/src/string/char.f90 new file mode 100644 index 000000000..bb7985ce5 --- /dev/null +++ b/numpy/f2py/tests/src/string/char.f90 @@ -0,0 +1,29 @@ +MODULE char_test + +CONTAINS + +SUBROUTINE change_strings(strings, n_strs, out_strings) + IMPLICIT NONE + + ! Inputs + INTEGER, INTENT(IN) :: n_strs + CHARACTER, INTENT(IN), DIMENSION(2,n_strs) :: strings + CHARACTER, INTENT(OUT), DIMENSION(2,n_strs) :: out_strings + +!f2py INTEGER, INTENT(IN) :: n_strs +!f2py CHARACTER, INTENT(IN), DIMENSION(2,n_strs) :: strings +!f2py CHARACTER, INTENT(OUT), DIMENSION(2,n_strs) :: strings + + ! Misc. + INTEGER*4 :: j + + + DO j=1, n_strs + out_strings(1,j) = strings(1,j) + out_strings(2,j) = 'A' + END DO + +END SUBROUTINE change_strings + +END MODULE char_test + diff --git a/numpy/f2py/tests/test_string.py b/numpy/f2py/tests/test_string.py new file mode 100644 index 000000000..10022ebb1 --- /dev/null +++ b/numpy/f2py/tests/test_string.py @@ -0,0 +1,26 @@ +from __future__ import division, absolute_import, print_function + +import os + +from numpy.testing import run_module_suite, assert_array_equal, dec +import numpy as np +import util + + +def _path(*a): + return os.path.join(*((os.path.dirname(__file__),) + a)) + +class TestString(util.F2PyTest): + sources = [_path('src', 'string', 'char.f90')] + + @dec.slow + def test_char(self): + strings = np.array(['ab', 'cd', 'ef'], dtype='c').T + inp, out = self.module.char_test.change_strings(strings, strings.shape[1]) + assert_array_equal(inp, strings) + expected = strings.copy() + expected[1, :] = 'AAA' + assert_array_equal(out, expected) + +if __name__ == "__main__": + run_module_suite() diff --git a/numpy/lib/arraysetops.py b/numpy/lib/arraysetops.py index fae3e3cbc..9a1448991 100644 --- a/numpy/lib/arraysetops.py +++ b/numpy/lib/arraysetops.py @@ -1,9 +1,10 @@ """ -Set operations for 1D numeric arrays based on sorting. +Set operations for arrays based on sorting. :Contains: - ediff1d, unique, + isin, + ediff1d, intersect1d, setxor1d, in1d, @@ -31,7 +32,7 @@ import numpy as np __all__ = [ 'ediff1d', 'intersect1d', 'setxor1d', 'union1d', 'setdiff1d', 'unique', - 'in1d' + 'in1d', 'isin' ] @@ -380,6 +381,7 @@ def setxor1d(ar1, ar2, assume_unique=False): flag2 = flag[1:] == flag[:-1] return aux[flag2] + def in1d(ar1, ar2, assume_unique=False, invert=False): """ Test whether each element of a 1-D array is also present in a second array. @@ -387,6 +389,8 @@ def in1d(ar1, ar2, assume_unique=False, invert=False): Returns a boolean array the same length as `ar1` that is True where an element of `ar1` is in `ar2` and False otherwise. + We recommend using :func:`isin` instead of `in1d` for new code. + Parameters ---------- ar1 : (M,) array_like @@ -411,6 +415,8 @@ def in1d(ar1, ar2, assume_unique=False, invert=False): See Also -------- + isin : Version of this function that preserves the + shape of ar1. numpy.lib.arraysetops : Module with a number of other functions for performing set operations on arrays. @@ -481,6 +487,96 @@ def in1d(ar1, ar2, assume_unique=False, invert=False): else: return ret[rev_idx] + +def isin(element, test_elements, assume_unique=False, invert=False): + """ + Calculates `element in test_elements`, broadcasting over `element` only. + Returns a boolean array of the same shape as `element` that is True + where an element of `element` is in `test_elements` and False otherwise. + + Parameters + ---------- + element : array_like + Input array. + test_elements : array_like + The values against which to test each value of `element`. + This argument is flattened if it is an array or array_like. + See notes for behavior with non-array-like parameters. + assume_unique : bool, optional + If True, the input arrays are both assumed to be unique, which + can speed up the calculation. Default is False. + invert : bool, optional + If True, the values in the returned array are inverted, as if + calculating `element not in test_elements`. Default is False. + ``np.isin(a, b, invert=True)`` is equivalent to (but faster + than) ``np.invert(np.isin(a, b))``. + + Returns + ------- + isin : ndarray, bool + Has the same shape as `element`. The values `element[isin]` + are in `test_elements`. + + See Also + -------- + in1d : Flattened version of this function. + numpy.lib.arraysetops : Module with a number of other functions for + performing set operations on arrays. + Notes + ----- + + `isin` is an element-wise function version of the python keyword `in`. + ``isin(a, b)`` is roughly equivalent to + ``np.array([item in b for item in a])`` if `a` and `b` are 1-D sequences. + + `element` and `test_elements` are converted to arrays if they are not + already. If `test_elements` is a set (or other non-sequence collection) + it will be converted to an object array with one element, rather than an + array of the values contained in `test_elements`. This is a consequence + of the `array` constructor's way of handling non-sequence collections. + Converting the set to a list usually gives the desired behavior. + + .. versionadded:: 1.13.0 + + Examples + -------- + >>> element = 2*np.arange(4).reshape((2, 2)) + >>> element + array([[0, 2], + [4, 6]]) + >>> test_elements = [1, 2, 4, 8] + >>> mask = np.isin(element, test_elements) + >>> mask + array([[ False, True], + [ True, False]], dtype=bool) + >>> element[mask] + array([2, 4]) + >>> mask = np.isin(element, test_elements, invert=True) + >>> mask + array([[ True, False], + [ False, True]], dtype=bool) + >>> element[mask] + array([0, 6]) + + Because of how `array` handles sets, the following does not + work as expected: + + >>> test_set = {1, 2, 4, 8} + >>> np.isin(element, test_set) + array([[ False, False], + [ False, False]], dtype=bool) + + Casting the set to a list gives the expected result: + + >>> np.isin(element, list(test_set)) + array([[ False, True], + [ True, False]], dtype=bool) + """ + element = np.asarray(element) + return in1d(element, test_elements, assume_unique=assume_unique, + invert=invert).reshape(element.shape) + + def union1d(ar1, ar2): """ Find the union of two arrays. diff --git a/numpy/lib/info.py b/numpy/lib/info.py index 141df2ace..e00406407 100644 --- a/numpy/lib/info.py +++ b/numpy/lib/info.py @@ -136,13 +136,15 @@ Threading Tricks ParallelExec Execute commands in parallel thread. ================ =================== -1D Array Set Operations +Array Set Operations ----------------------- -Set operations for 1D numeric arrays based on sort() function. +Set operations for numeric arrays based on sort() function. ================ =================== -ediff1d Array difference (auxiliary function). unique Unique elements of an array. +isin Test whether each element of an ND array is present + anywhere within a second array. +ediff1d Array difference (auxiliary function). intersect1d Intersection of 1D arrays with unique elements. setxor1d Set exclusive-or of 1D arrays with unique elements. in1d Test whether elements in a 1D array are also present in diff --git a/numpy/lib/tests/test_arraysetops.py b/numpy/lib/tests/test_arraysetops.py index eb4cca0ce..fa664ff24 100644 --- a/numpy/lib/tests/test_arraysetops.py +++ b/numpy/lib/tests/test_arraysetops.py @@ -8,7 +8,7 @@ from numpy.testing import ( run_module_suite, TestCase, assert_array_equal, assert_equal, assert_raises ) from numpy.lib.arraysetops import ( - ediff1d, intersect1d, setxor1d, union1d, setdiff1d, unique, in1d + ediff1d, intersect1d, setxor1d, union1d, setdiff1d, unique, in1d, isin ) @@ -77,6 +77,46 @@ class TestSetOps(TestCase): assert(isinstance(ediff1d(np.matrix(1)), np.matrix)) assert(isinstance(ediff1d(np.matrix(1), to_begin=1), np.matrix)) + def test_isin(self): + # the tests for in1d cover most of isin's behavior + # if in1d is removed, would need to change those tests to test + # isin instead. + def _isin_slow(a, b): + b = np.asarray(b).flatten().tolist() + return a in b + isin_slow = np.vectorize(_isin_slow, otypes=[bool], excluded={1}) + def assert_isin_equal(a, b): + x = isin(a, b) + y = isin_slow(a, b) + assert_array_equal(x, y) + + #multidimensional arrays in both arguments + a = np.arange(24).reshape([2, 3, 4]) + b = np.array([[10, 20, 30], [0, 1, 3], [11, 22, 33]]) + assert_isin_equal(a, b) + + #array-likes as both arguments + c = [(9, 8), (7, 6)] + d = (9, 7) + assert_isin_equal(c, d) + + #zero-d array: + f = np.array(3) + assert_isin_equal(f, b) + assert_isin_equal(a, f) + assert_isin_equal(f, f) + + #scalar: + assert_isin_equal(5, b) + assert_isin_equal(a, 6) + assert_isin_equal(5, 6) + + #empty array-like: + x = [] + assert_isin_equal(x, b) + assert_isin_equal(a, x) + assert_isin_equal(x, x) + def test_in1d(self): # we use two different sizes for the b array here to test the # two different paths in in1d(). diff --git a/numpy/ma/extras.py b/numpy/ma/extras.py index 4955d25eb..e100e471c 100644 --- a/numpy/ma/extras.py +++ b/numpy/ma/extras.py @@ -16,7 +16,7 @@ __all__ = [ 'column_stack', 'compress_cols', 'compress_nd', 'compress_rowcols', 'compress_rows', 'count_masked', 'corrcoef', 'cov', 'diagflat', 'dot', 'dstack', 'ediff1d', 'flatnotmasked_contiguous', 'flatnotmasked_edges', - 'hsplit', 'hstack', 'in1d', 'intersect1d', 'mask_cols', 'mask_rowcols', + 'hsplit', 'hstack', 'isin', 'in1d', 'intersect1d', 'mask_cols', 'mask_rowcols', 'mask_rows', 'masked_all', 'masked_all_like', 'median', 'mr_', 'notmasked_contiguous', 'notmasked_edges', 'polyfit', 'row_stack', 'setdiff1d', 'setxor1d', 'unique', 'union1d', 'vander', 'vstack', @@ -1131,6 +1131,7 @@ def setxor1d(ar1, ar2, assume_unique=False): flag2 = (flag[1:] == flag[:-1]) return aux[flag2] + def in1d(ar1, ar2, assume_unique=False, invert=False): """ Test whether each element of an array is also present in a second @@ -1138,8 +1139,11 @@ def in1d(ar1, ar2, assume_unique=False, invert=False): The output is always a masked array. See `numpy.in1d` for more details. + We recommend using :func:`isin` instead of `in1d` for new code. + See Also -------- + isin : Version of this function that preserves the shape of ar1. numpy.in1d : Equivalent function for ndarrays. Notes @@ -1170,6 +1174,29 @@ def in1d(ar1, ar2, assume_unique=False, invert=False): return flag[indx][rev_idx] +def isin(element, test_elements, assume_unique=False, invert=False): + """ + Calculates `element in test_elements`, broadcasting over + `element` only. + + The output is always a masked array of the same shape as `element`. + See `numpy.isin` for more details. + + See Also + -------- + in1d : Flattened version of this function. + numpy.isin : Equivalent function for ndarrays. + + Notes + ----- + .. versionadded:: 1.13.0 + + """ + element = ma.asarray(element) + return in1d(element, test_elements, assume_unique=assume_unique, + invert=invert).reshape(element.shape) + + def union1d(ar1, ar2): """ Union of two arrays. diff --git a/numpy/ma/tests/test_extras.py b/numpy/ma/tests/test_extras.py index 77a5c0fc6..e7ebd8b82 100644 --- a/numpy/ma/tests/test_extras.py +++ b/numpy/ma/tests/test_extras.py @@ -28,7 +28,7 @@ from numpy.ma.extras import ( median, average, unique, setxor1d, setdiff1d, union1d, intersect1d, in1d, ediff1d, apply_over_axes, apply_along_axis, compress_nd, compress_rowcols, mask_rowcols, clump_masked, clump_unmasked, flatnotmasked_contiguous, - notmasked_contiguous, notmasked_edges, masked_all, masked_all_like, + notmasked_contiguous, notmasked_edges, masked_all, masked_all_like, isin, diagflat ) import numpy.ma.extras as mae @@ -1435,6 +1435,27 @@ class TestArraySetOps(TestCase): # assert_array_equal([], setxor1d([], [])) + def test_isin(self): + # the tests for in1d cover most of isin's behavior + # if in1d is removed, would need to change those tests to test + # isin instead. + a = np.arange(24).reshape([2, 3, 4]) + mask = np.zeros([2, 3, 4]) + mask[1, 2, 0] = 1 + a = array(a, mask=mask) + b = array(data=[0, 10, 20, 30, 1, 3, 11, 22, 33], + mask=[0, 1, 0, 1, 0, 1, 0, 1, 0]) + ec = zeros((2, 3, 4), dtype=bool) + ec[0, 0, 0] = True + ec[0, 0, 1] = True + ec[0, 2, 3] = True + c = isin(a, b) + assert_(isinstance(c, MaskedArray)) + assert_array_equal(c, ec) + #compare results of np.isin to ma.isin + d = np.isin(a, b[~b.mask]) & ~a.mask + assert_array_equal(c, d) + def test_in1d(self): # Test in1d a = array([1, 2, 5, 7, -1], mask=[0, 0, 0, 0, 1]) |