# 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:
# 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:
# 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.
# 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
# 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
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:
# 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)
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, ))
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
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, ))
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]])]