# Concatenation And Splitting

## 1. CONCATENATING ARRAYS

`np.concatenate()` constructor is used to concatenate or join two or more arrays into one. The only required argument is list or tuple of arrays.

💡For `np.concatenate()` to work, all the input array dimensions for the concatenation axis must match exactly, other wise you will get `ValueError`

### 1.1. 1D arrays

Let’s get straight to work and define two arrays and then join them:

```python
# first, import numpy
import numpy as np

# making two arrays to concatenate
arr1 = np.arange(1,4)
arr2 = np.arange(4,7)

print("Arrays to concatenate:")
print(arr1);print(arr2)

print("After concatenation:")
print(np.concatenate([arr1,arr2]))
```

```
Arrays to concatenate:
[1 2 3]
[4 5 6]
After concatenation:
[1 2 3 4 5 6]
```

We can also join more than two arrays:

```python
# we can concatenate more than two arrays
arr3 = np.arange(7,10)

np.concatenate([arr1,arr2,arr3])
```

```
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
```

### 1.2. nD arrays

The same constructor is used for joining two or more nD arrays

By default, `np.concat` joins along the row wise (stack on top of each other). However, by providing kwarg `axis=1`, we can also concatenate along columns.

```python
# creating 2D arrays to join
ar2d1 = np.arange(1,7).reshape(2,3)
ar2d2 = np.arange(7,13).reshape(2,3)
print("2D arrays to concatenate:")
print(ar2d1);print(ar2d2)

# case1: joining along rows
print("\nConcatenating along rows:")
print(np.concatenate([ar2d1,ar2d2]))

# case1: joining along columns
print("\nConcatenating along columns:")
print(np.concatenate([ar2d1,ar2d2], axis=1))
```

```
2D arrays to concatenate:
[[1 2 3]
 [4 5 6]]
[[ 7  8  9]
 [10 11 12]]

Concatenating along rows:
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]

Concatenating along columns:
[[ 1  2  3  7  8  9]
 [ 4  5  6 10 11 12]]
```

## 2. CONCATENATING ARRAYS OF MIXED DIMENSIONS

To concatenate arrays of mixed dimensions we will use `np.vstack` and `np.hstack` functions

### 2.1. Vertical Stack ( np.vstack)

> `np.vstack` concatenate along rows, stack vertically.

💡 To make `vstack` work, all input arrays should have same number of columns

```python
# defining two 2D arrays
v1 = np.arange(8).reshape(2,4)
v2 = np.arange(20).reshape(5,4)

print("We vstack these arrays:\n")
print(f"Array 1: \n{v1}");print(f"Array 2: \n{v2}") 

# vstack
print("\nAfter vStack-ed both arrays")
print(np.vstack([v1,v2]))
```

```
We will vstack these arrays:

Array 1: 
[[0 1 2 3]
 [4 5 6 7]]
Array 2: 
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]

After vStack-ed both arrays
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]
```

### 2.1. Horizontal Stack ( np.hstack)

> `np.hstack` concatenate along columns, stack one after the other.

💡To make `hstack` work, both arrays should have same number of rows

```python
h1 = np.arange(10).reshape(5,2)
h2 = np.arange(30).reshape(5,6)
print("We will hstack these arrays:\n")
print(f"Array 1: \n{h1}");print(f"Array 2: \n{h2}") 

print("\nAfter hStack-ed both arrays")
print(np.hstack([h1,h2]))
```

```
We will hstack these arrays:

Array 1: 
[[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]
Array 2: 
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]
 [24 25 26 27 28 29]]

After hStack-ed both arrays
[[ 0  1  0  1  2  3  4  5]
 [ 2  3  6  7  8  9 10 11]
 [ 4  5 12 13 14 15 16 17]
 [ 6  7 18 19 20 21 22 23]
 [ 8  9 24 25 26 27 28 29]]
```

> `np.dstack` is used for stacking 3 arrays

## 3. SPLITTING

Opposite of concatenation is splitting and we will use `np.split`, `np.vsplit` and `np.hsplit` to split the arrays

### 3.1. np.split

> `np.split` for N split points creates N+1, sub-arrays `np.split(x,y)` will split the array into three subarray. An example will make the concept clearer:

```python
# array to splt
ns = np.arange(1,10)

print(f"Original array: {ns}")
print("After split:")
ns1,ns2,ns3 = np.split(ns, (3,6))
print(ns1);print(ns2);print(ns3)
```

```
Original array: [1 2 3 4 5 6 7 8 9]
After nsplit:
[1 2 3]
[4 5 6]
[7 8 9]
```

### 3.2… np.vsplit

> `np.vsplit` splits along the vertical axis. You can either provide:

* `integer` of equally shaped array (method 1 below), or,
* by specifying the *row* `[integer]` at which the division should occur (method 2 below)

```python
vs = np.arange(16).reshape(4,4)
print(f"Array to vSplit: \n{vs}")

# method 1, by specifying number of equally shaped arrays
print("\nUsing Method 1")
upper, lower = np.vsplit(vs, 2)
print(f"Upper: \n{upper}\nLower: \n{lower}")

# method 2, by specifying row, after which division should occur
print("\nUsing Method 2")
print(np.vsplit(vs, [2]))
```

```
Array to vSplit: 
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]

Using Method 1
Upper: 
[[0 1 2 3]
 [4 5 6 7]]
Lower: 
[[ 8  9 10 11]
 [12 13 14 15]]

Using Method 2
[array([[0, 1, 2, 3],
       [4, 5, 6, 7]]), array([[ 8,  9, 10, 11],
       [12, 13, 14, 15]])]
```

### 3.3. np.hsplit

> `np.hsplit` splits along the horizontal axis. You can either provide:

* number `integer` of equally shaped array, or,
* by specifying the *column* `[integer]` at which the division should occur

```python
hs = np.arange(16).reshape(2,8)
print(f"Array to hSplit: \n{hs}")

# method 1, by specifying number of equally shaped arrays
print("\nUsing Method 1")
right, left = np.hsplit(hs, 2)
print(f"Right: \n{right}\nLeft: \n{left}")

# method 2, by specifying columns, after which division should occur
print("\nUsing Method 2")
print(np.hsplit(hs, [4]))
```

```
Array to hSplit: 
[[ 0  1  2  3  4  5  6  7]
 [ 8  9 10 11 12 13 14 15]]

Using Method 1
Right: 
[[ 0  1  2  3]
 [ 8  9 10 11]]
Left: 
[[ 4  5  6  7]
 [12 13 14 15]]

Using Method 2
[array([[ 0,  1,  2,  3],
       [ 8,  9, 10, 11]]), array([[ 4,  5,  6,  7],
       [12, 13, 14, 15]])]
```
