From 3d4c5d19a8f15b35df50d945b9c8853b683f7ab6 Mon Sep 17 00:00:00 2001 From: Stefan Otte Date: Sun, 7 Sep 2014 16:47:55 +0200 Subject: [PATCH 1/7] ENH: add block() function. `block` is similar to Matlab's square bracket stacking functionality for block matices. `block` is an addition to the current stacking functions vstack, hstack, stack. --- numpy/core/shape_base.py | 91 ++++++++++++++++++++++++++++- numpy/core/tests/test_shape_base.py | 77 ++++++++++++++++++++++-- 2 files changed, 161 insertions(+), 7 deletions(-) diff --git a/numpy/core/shape_base.py b/numpy/core/shape_base.py index 599b48d82b50..6dffb0a59d68 100644 --- a/numpy/core/shape_base.py +++ b/numpy/core/shape_base.py @@ -1,10 +1,11 @@ from __future__ import division, absolute_import, print_function -__all__ = ['atleast_1d', 'atleast_2d', 'atleast_3d', 'vstack', 'hstack', - 'stack'] +__all__ = ['atleast_1d', 'atleast_2d', 'atleast_3d', 'block', 'hstack', + 'stack', 'vstack'] + from . import numeric as _nx -from .numeric import asanyarray, newaxis +from .numeric import array, asanyarray, newaxis def atleast_1d(*arys): """ @@ -202,6 +203,7 @@ def vstack(tup): dstack : Stack arrays in sequence depth wise (along third dimension). concatenate : Join a sequence of arrays along an existing axis. vsplit : Split array into a list of multiple sub-arrays vertically. + block : Create block arrays. Notes ----- @@ -253,6 +255,7 @@ def hstack(tup): dstack : Stack arrays in sequence depth wise (along third axis). concatenate : Join a sequence of arrays along an existing axis. hsplit : Split array along second axis. + block : Create block arrays. Notes ----- @@ -279,6 +282,7 @@ def hstack(tup): else: return _nx.concatenate(arrs, 1) + def stack(arrays, axis=0): """ Join a sequence of arrays along a new axis. @@ -305,6 +309,7 @@ def stack(arrays, axis=0): -------- concatenate : Join a sequence of arrays along an existing axis. split : Split array into a list of multiple sub-arrays of equal size. + block : Create block arrays. Examples -------- @@ -348,3 +353,83 @@ def stack(arrays, axis=0): sl = (slice(None),) * axis + (_nx.newaxis,) expanded_arrays = [arr[sl] for arr in arrays] return _nx.concatenate(expanded_arrays, axis=axis) + + +def block(tup_tup): + """ + Create block arrays similar to Matlab's "square bracket stacking": + + [A A; B B] + + You can create a block array with the same notation you use for + `np.array`. + + Parameters + ---------- + tup_tup : sequence of sequence of ndarrays + 1-D arrays are treated as row vectors. + + Returns + ------- + stacked : ndarray + The 2-D array formed by stacking the given arrays. + + See Also + -------- + stack : Stack arrays in sequence along a new dimension. + hstack : Stack arrays in sequence horizontally (column wise). + vstack : Stack arrays in sequence vertically (row wise). + dstack : Stack arrays in sequence depth wise (along third dimension). + concatenate : Join a sequence of arrays together. + vsplit : Split array into a list of multiple sub-arrays vertically. + + Examples + -------- + Stacking in a row: + >>> A = np.array([[1, 2, 3]]) + >>> B = np.array([[2, 3, 4]]) + >>> block([A, B]) + array([[1, 2, 3, 2, 3, 4]]) + + >>> A = np.array([[1, 1], [1, 1]]) + >>> B = 2 * A + >>> block([A, B]) + array([[1, 1, 2, 2], + [1, 1, 2, 2]]) + + >>> # the tuple notation also works + >>> block((A, B)) + array([[1, 1, 2, 2], + [1, 1, 2, 2]]) + + >>> # block matrix with arbitrary shaped elements + >>> One = np.array([[1, 1, 1]]) + >>> Two = np.array([[2, 2, 2]]) + >>> Three = np.array([[3, 3, 3, 3, 3, 3]]) + >>> four = np.array([4, 4, 4, 4, 4, 4]) + >>> five = np.array([5]) + >>> six = np.array([6, 6, 6, 6, 6]) + >>> Zeros = np.zeros((2, 6), dtype=int) + >>> block([[One, Two], + ... [Three], + ... [four], + ... [five, six], + ... [Zeros]]) + array([[1, 1, 1, 2, 2, 2], + [3, 3, 3, 3, 3, 3], + [4, 4, 4, 4, 4, 4], + [5, 6, 6, 6, 6, 6], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0]]) + + >>> # 1-D vectors are treated as row arrays + >>> a = np.array([1, 1]) + >>> b = np.array([2, 2]) + >>> block([a, b]) + array([[1, 1, 2, 2]]) + """ + if isinstance(tup_tup[0], list) or isinstance(tup_tup[0], tuple): + result = vstack([hstack(row) for row in tup_tup]) + else: + result = hstack(tup_tup) + return atleast_2d(result) diff --git a/numpy/core/tests/test_shape_base.py b/numpy/core/tests/test_shape_base.py index 0d163c1dc9bf..c4990788cba2 100644 --- a/numpy/core/tests/test_shape_base.py +++ b/numpy/core/tests/test_shape_base.py @@ -1,11 +1,14 @@ from __future__ import division, absolute_import, print_function +import warnings import numpy as np -from numpy.compat import long from numpy.core import (array, arange, atleast_1d, atleast_2d, atleast_3d, - vstack, hstack, newaxis, concatenate, stack) -from numpy.testing import (TestCase, assert_, assert_raises, assert_array_equal, - assert_equal, run_module_suite, assert_raises_regex) + block, vstack, hstack, newaxis, concatenate, stack) +from numpy.testing import (TestCase, assert_, assert_raises, + assert_array_equal, assert_equal, run_module_suite, + assert_raises_regex, assert_almost_equal) + +from numpy.compat import long class TestAtleast1d(TestCase): def test_0D_array(self): @@ -315,5 +318,71 @@ def test_stack(): stack, [m, m]) +class TestBlock(TestCase): + def test_block_row_wise(self): + A = np.ones((2, 2)) + B = 2 * A + assert_almost_equal(block([A, B]), + np.array([[1, 1, 2, 2], + [1, 1, 2, 2]])) + # tuple notation + assert_almost_equal(block((A, B)), + np.array([[1, 1, 2, 2], + [1, 1, 2, 2]])) + + def test_block_column_wise(self): + A = np.ones((2, 2)) + B = 2 * A + assert_almost_equal(block([[A], [B]]), + np.array([[1, 1], + [1, 1], + [2, 2], + [2, 2]])) + # tuple notation with only one element per tuple does not make much + # sense. test it anyway just to make sure + assert_almost_equal(block(((A, ), (B, ))), + np.array([[1, 1], + [1, 1], + [2, 2], + [2, 2]])) + + def test_block_complex(self): + # # # a bit more complex + One = np.array([[1, 1, 1]]) + Two = np.array([[2, 2, 2]]) + Three = np.array([[3, 3, 3, 3, 3, 3]]) + four = np.array([4, 4, 4, 4, 4, 4]) + five = np.array([5]) + six = np.array([6, 6, 6, 6, 6]) + Zeros = np.zeros((2, 6)) + result = block([[One, Two], + [Three], + [four], + [five, six], + [Zeros]]) + expected = np.array([[1, 1, 1, 2, 2, 2], + [3, 3, 3, 3, 3, 3], + [4, 4, 4, 4, 4, 4], + [5, 6, 6, 6, 6, 6], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0]]) + assert_almost_equal(result, expected) + + def test_block_with_1d_arrays(self): + # # # 1-D vectors are treated as row arrays + a = np.array([1, 2, 3]) + b = np.array([2, 3, 4]) + assert_almost_equal(block([a, b]), + np.array([[1, 2, 3, 2, 3, 4]])) + assert_almost_equal(block([[a], [b]]), + np.array([[1, 2, 3], + [2, 3, 4]])) + a = np.array([1, 2, 3]) + b = np.array([2, 3, 4]) + assert_almost_equal(block([[a, b], [a, b]]), + np.array([[1, 2, 3, 2, 3, 4], + [1, 2, 3, 2, 3, 4]])) + + if __name__ == "__main__": run_module_suite() From 9de92666b72050af271b375ea8b7fdb9d52fabdc Mon Sep 17 00:00:00 2001 From: Stefan Otte Date: Thu, 21 Jan 2016 13:02:19 +0100 Subject: [PATCH 2/7] Move comment about matlab notation comment to Note section. --- numpy/core/shape_base.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/numpy/core/shape_base.py b/numpy/core/shape_base.py index 6dffb0a59d68..ad9f8152be78 100644 --- a/numpy/core/shape_base.py +++ b/numpy/core/shape_base.py @@ -357,9 +357,7 @@ def stack(arrays, axis=0): def block(tup_tup): """ - Create block arrays similar to Matlab's "square bracket stacking": - - [A A; B B] + Create a block array consisting of other arrays. You can create a block array with the same notation you use for `np.array`. @@ -383,6 +381,10 @@ def block(tup_tup): concatenate : Join a sequence of arrays together. vsplit : Split array into a list of multiple sub-arrays vertically. + Notes + ----- + ``block`` is similar to Matlab's "square bracket stacking": ``[A A; B B]`` + Examples -------- Stacking in a row: @@ -427,6 +429,7 @@ def block(tup_tup): >>> b = np.array([2, 2]) >>> block([a, b]) array([[1, 1, 2, 2]]) + """ if isinstance(tup_tup[0], list) or isinstance(tup_tup[0], tuple): result = vstack([hstack(row) for row in tup_tup]) From a3765e7a6d3502489880a1a51c758444709aa3ee Mon Sep 17 00:00:00 2001 From: Stefan Otte Date: Thu, 21 Jan 2016 13:09:06 +0100 Subject: [PATCH 3/7] rename tup_tup to arrays --- numpy/core/shape_base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/numpy/core/shape_base.py b/numpy/core/shape_base.py index ad9f8152be78..cc0bf1dd17b7 100644 --- a/numpy/core/shape_base.py +++ b/numpy/core/shape_base.py @@ -355,7 +355,7 @@ def stack(arrays, axis=0): return _nx.concatenate(expanded_arrays, axis=axis) -def block(tup_tup): +def block(arrays): """ Create a block array consisting of other arrays. @@ -364,7 +364,7 @@ def block(tup_tup): Parameters ---------- - tup_tup : sequence of sequence of ndarrays + arrays : sequence of sequence of ndarrays 1-D arrays are treated as row vectors. Returns @@ -431,8 +431,8 @@ def block(tup_tup): array([[1, 1, 2, 2]]) """ - if isinstance(tup_tup[0], list) or isinstance(tup_tup[0], tuple): - result = vstack([hstack(row) for row in tup_tup]) + if isinstance(arrays[0], list) or isinstance(arrays[0], tuple): + result = vstack([hstack(row) for row in arrays]) else: - result = hstack(tup_tup) + result = hstack(arrays) return atleast_2d(result) From a529167362b9d85c23318c372f3d1694a23db886 Mon Sep 17 00:00:00 2001 From: Stefan Otte Date: Thu, 21 Jan 2016 13:09:48 +0100 Subject: [PATCH 4/7] Simplify if --- numpy/core/shape_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/core/shape_base.py b/numpy/core/shape_base.py index cc0bf1dd17b7..ce9b135a5446 100644 --- a/numpy/core/shape_base.py +++ b/numpy/core/shape_base.py @@ -431,7 +431,7 @@ def block(arrays): array([[1, 1, 2, 2]]) """ - if isinstance(arrays[0], list) or isinstance(arrays[0], tuple): + if isinstance(arrays[0], (list, tuple)): result = vstack([hstack(row) for row in arrays]) else: result = hstack(arrays) From a716e4d4e597cfd0f45e8278c0609b1c45513f50 Mon Sep 17 00:00:00 2001 From: Stefan Otte Date: Thu, 21 Jan 2016 13:12:58 +0100 Subject: [PATCH 5/7] Better examples in docstring --- numpy/core/shape_base.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/numpy/core/shape_base.py b/numpy/core/shape_base.py index ce9b135a5446..968c1afc05c0 100644 --- a/numpy/core/shape_base.py +++ b/numpy/core/shape_base.py @@ -393,18 +393,30 @@ def block(arrays): >>> block([A, B]) array([[1, 2, 3, 2, 3, 4]]) + 1-D vectors are treated as row arrays + >>> a = np.array([1, 1]) + >>> b = np.array([2, 2]) + >>> block([a, b]) + array([[1, 1, 2, 2]]) + + >>> a = np.array([1, 1]) + >>> b = np.array([2, 2]) + >>> block([[a], [b]]) + array([[1, 1], + [2, 2]]) + >>> A = np.array([[1, 1], [1, 1]]) >>> B = 2 * A >>> block([A, B]) array([[1, 1, 2, 2], [1, 1, 2, 2]]) - >>> # the tuple notation also works + The tuple notation also works: >>> block((A, B)) array([[1, 1, 2, 2], [1, 1, 2, 2]]) - >>> # block matrix with arbitrary shaped elements + Block matrix with arbitrary shaped elements >>> One = np.array([[1, 1, 1]]) >>> Two = np.array([[2, 2, 2]]) >>> Three = np.array([[3, 3, 3, 3, 3, 3]]) @@ -424,12 +436,6 @@ def block(arrays): [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]) - >>> # 1-D vectors are treated as row arrays - >>> a = np.array([1, 1]) - >>> b = np.array([2, 2]) - >>> block([a, b]) - array([[1, 1, 2, 2]]) - """ if isinstance(arrays[0], (list, tuple)): result = vstack([hstack(row) for row in arrays]) From 3dcedce925896e1ac883dcd84790a7a142fbc3f4 Mon Sep 17 00:00:00 2001 From: Stefan Otte Date: Mon, 8 Feb 2016 09:14:03 +0100 Subject: [PATCH 6/7] *args version of block --- numpy/core/shape_base.py | 45 +++++++---- numpy/core/tests/test_shape_base.py | 115 ++++++++++++++++++---------- 2 files changed, 103 insertions(+), 57 deletions(-) diff --git a/numpy/core/shape_base.py b/numpy/core/shape_base.py index 968c1afc05c0..0ba0dfd37324 100644 --- a/numpy/core/shape_base.py +++ b/numpy/core/shape_base.py @@ -355,7 +355,7 @@ def stack(arrays, axis=0): return _nx.concatenate(expanded_arrays, axis=axis) -def block(arrays): +def block(*arrays): """ Create a block array consisting of other arrays. @@ -393,6 +393,13 @@ def block(arrays): >>> block([A, B]) array([[1, 2, 3, 2, 3, 4]]) + Stacking in a column: + >>> A = np.array([[1, 2, 3]]) + >>> B = np.array([[2, 3, 4]]) + >>> block(A, B) + array([[1, 2, 3], + [2, 3, 4]]) + 1-D vectors are treated as row arrays >>> a = np.array([1, 1]) >>> b = np.array([2, 2]) @@ -401,17 +408,13 @@ def block(arrays): >>> a = np.array([1, 1]) >>> b = np.array([2, 2]) - >>> block([[a], [b]]) + >>> block(a, b) array([[1, 1], [2, 2]]) - >>> A = np.array([[1, 1], [1, 1]]) - >>> B = 2 * A - >>> block([A, B]) - array([[1, 1, 2, 2], - [1, 1, 2, 2]]) - The tuple notation also works: + >>> A = np.ones((2, 2)) + >>> B = 2 * A >>> block((A, B)) array([[1, 1, 2, 2], [1, 1, 2, 2]]) @@ -424,11 +427,11 @@ def block(arrays): >>> five = np.array([5]) >>> six = np.array([6, 6, 6, 6, 6]) >>> Zeros = np.zeros((2, 6), dtype=int) - >>> block([[One, Two], - ... [Three], - ... [four], + >>> block([One, Two], + ... Three, + ... four, ... [five, six], - ... [Zeros]]) + ... Zeros) array([[1, 1, 1, 2, 2, 2], [3, 3, 3, 3, 3, 3], [4, 4, 4, 4, 4, 4], @@ -436,9 +439,19 @@ def block(arrays): [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]) + """ - if isinstance(arrays[0], (list, tuple)): - result = vstack([hstack(row) for row in arrays]) + if len(arrays) < 1: + raise TypeError("need at least one array to create a block array") + + result = [] + for row in arrays: + if isinstance(row, (list, tuple)): + result.append(hstack(row)) + else: + result.append(row) + + if len(result) > 1: + return vstack(result) else: - result = hstack(arrays) - return atleast_2d(result) + return atleast_2d(result[0]) diff --git a/numpy/core/tests/test_shape_base.py b/numpy/core/tests/test_shape_base.py index c4990788cba2..2c4c667ed92b 100644 --- a/numpy/core/tests/test_shape_base.py +++ b/numpy/core/tests/test_shape_base.py @@ -319,32 +319,70 @@ def test_stack(): class TestBlock(TestCase): - def test_block_row_wise(self): + def test_block_simple_row_wise(self): A = np.ones((2, 2)) B = 2 * A - assert_almost_equal(block([A, B]), - np.array([[1, 1, 2, 2], - [1, 1, 2, 2]])) - # tuple notation - assert_almost_equal(block((A, B)), - np.array([[1, 1, 2, 2], - [1, 1, 2, 2]])) - - def test_block_column_wise(self): + desired = np.array([[1, 1, 2, 2], + [1, 1, 2, 2]]) + result = block([A, B]) + assert_almost_equal(desired, result) + # with tuples + result = block((A, B)) + assert_almost_equal(desired, result) + + def test_block_simple_column_wise(self): A = np.ones((2, 2)) B = 2 * A - assert_almost_equal(block([[A], [B]]), - np.array([[1, 1], - [1, 1], - [2, 2], - [2, 2]])) - # tuple notation with only one element per tuple does not make much - # sense. test it anyway just to make sure - assert_almost_equal(block(((A, ), (B, ))), - np.array([[1, 1], - [1, 1], - [2, 2], - [2, 2]])) + expected = np.array([[1, 1], + [1, 1], + [2, 2], + [2, 2]]) + result = block(A, B) + assert_almost_equal(expected, result) + + def test_block_needless_brackts(self): + A = np.ones((2, 2)) + B = 2 * A + expected = np.array([[1, 1], + [1, 1], + [2, 2], + [2, 2]]) + result = block([A], [B]) # here are the needless brackets + assert_almost_equal(expected, result) + + def test_block_with_1d_arrays_row_wise(self): + # # # 1-D vectors are treated as row arrays + a = np.array([1, 2, 3]) + b = np.array([2, 3, 4]) + expected = np.array([[1, 2, 3, 2, 3, 4]]) + result = block([a, b]) + assert_almost_equal(expected, result) + + def test_block_with_1d_arrays_multiple_rows(self): + a = np.array([1, 2, 3]) + b = np.array([2, 3, 4]) + expected = np.array([[1, 2, 3, 2, 3, 4], + [1, 2, 3, 2, 3, 4]]) + result = block([a, b], [a, b]) + assert_almost_equal(expected, result) + + def test_block_with_1d_arrays_column_wise(self): + # # # 1-D vectors are treated as row arrays + a = np.array([1, 2, 3]) + b = np.array([2, 3, 4]) + expected = np.array([[1, 2, 3], + [2, 3, 4]]) + result = block(a, b) + assert_almost_equal(expected, result) + + def test_block_mixed_1d_and_2d(self): + A = np.ones((2, 2)) + B = np.array([2, 2]) + result = block(A, B) + expected = np.array([[1, 1], + [1, 1], + [2, 2]]) + assert_almost_equal(expected, result) def test_block_complex(self): # # # a bit more complex @@ -355,11 +393,12 @@ def test_block_complex(self): five = np.array([5]) six = np.array([6, 6, 6, 6, 6]) Zeros = np.zeros((2, 6)) - result = block([[One, Two], - [Three], - [four], - [five, six], - [Zeros]]) + + result = block([One, Two], + Three, + four, + [five, six], + Zeros) expected = np.array([[1, 1, 1, 2, 2, 2], [3, 3, 3, 3, 3, 3], [4, 4, 4, 4, 4, 4], @@ -368,20 +407,14 @@ def test_block_complex(self): [0, 0, 0, 0, 0, 0]]) assert_almost_equal(result, expected) - def test_block_with_1d_arrays(self): - # # # 1-D vectors are treated as row arrays - a = np.array([1, 2, 3]) - b = np.array([2, 3, 4]) - assert_almost_equal(block([a, b]), - np.array([[1, 2, 3, 2, 3, 4]])) - assert_almost_equal(block([[a], [b]]), - np.array([[1, 2, 3], - [2, 3, 4]])) - a = np.array([1, 2, 3]) - b = np.array([2, 3, 4]) - assert_almost_equal(block([[a, b], [a, b]]), - np.array([[1, 2, 3, 2, 3, 4], - [1, 2, 3, 2, 3, 4]])) + def test_block_with_mismatched_shape(self): + a = np.array([0, 0]) + b = np.eye(2) + assert_raises(ValueError, np.block, (a, b)) + assert_raises(ValueError, np.block, (b, a)) + + def test_not_list_or_tuple_as_input(self): + assert_raises(TypeError, np.block) if __name__ == "__main__": From 8b97ad965548d9c028907390f483c1ede6e445dd Mon Sep 17 00:00:00 2001 From: Stefan Otte Date: Wed, 10 Feb 2016 10:07:15 +0100 Subject: [PATCH 7/7] Unittest for unneeded [] --- numpy/core/tests/test_shape_base.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/numpy/core/tests/test_shape_base.py b/numpy/core/tests/test_shape_base.py index 2c4c667ed92b..ee681e99e12a 100644 --- a/numpy/core/tests/test_shape_base.py +++ b/numpy/core/tests/test_shape_base.py @@ -407,6 +407,28 @@ def test_block_complex(self): [0, 0, 0, 0, 0, 0]]) assert_almost_equal(result, expected) + # additional [] around rows should not have any influence + result = block([One, Two], + Three, + [four], + [five, six], + Zeros) + assert_almost_equal(result, expected) + + result = block([One, Two], + [Three], + [four], + [five, six], + Zeros) + assert_almost_equal(result, expected) + + result = block([One, Two], + [Three], + [four], + [five, six], + [Zeros]) + assert_almost_equal(result, expected) + def test_block_with_mismatched_shape(self): a = np.array([0, 0]) b = np.eye(2)