summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/release/1.13.0-notes.rst16
-rw-r--r--doc/source/reference/routines.set.rst1
-rw-r--r--numpy/add_newdocs.py2
-rw-r--r--numpy/core/arrayprint.py11
-rw-r--r--numpy/core/include/numpy/ndarraytypes.h15
-rw-r--r--numpy/core/src/multiarray/arraytypes.c.src12
-rw-r--r--numpy/core/src/multiarray/multiarray_tests.c.src11
-rw-r--r--numpy/core/tests/test_arrayprint.py8
-rw-r--r--numpy/core/tests/test_deprecations.py8
-rw-r--r--numpy/distutils/ccompiler.py22
-rw-r--r--numpy/distutils/misc_util.py12
-rw-r--r--numpy/distutils/unixccompiler.py5
-rw-r--r--numpy/f2py/capi_maps.py9
-rw-r--r--numpy/f2py/cfuncs.py3
-rw-r--r--numpy/f2py/src/fortranobject.c28
-rw-r--r--numpy/f2py/tests/src/string/char.f9029
-rw-r--r--numpy/f2py/tests/test_string.py26
-rw-r--r--numpy/lib/arraysetops.py102
-rw-r--r--numpy/lib/info.py8
-rw-r--r--numpy/lib/tests/test_arraysetops.py42
-rw-r--r--numpy/ma/extras.py29
-rw-r--r--numpy/ma/tests/test_extras.py23
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])