If you are willing to start from a NumPy array, you could adapt the solutions from here, omitting the senseless ones like those using zip() or the key parameter from min()/max()), and adding a couple more:
def extrema_py(arr):
    return max(arr[:, 0]), min(arr[:, 0]), max(arr[:, 1]), min(arr[:, 1])
import numpy as np
def extrema_np(arr):
    return np.max(arr[:, 0]), np.min(arr[:, 0]), np.max(arr[:, 1]), np.min(arr[:, 1])
import numpy as np
def extrema_npt(arr):
    arr = arr.transpose()
    return np.max(arr[0, :]), np.min(arr[0, :]), np.max(arr[1, :]), np.min(arr[1, :])
import numpy as np
def extrema_npa(arr):
    x_max, y_max = np.max(arr, axis=0)
    x_min, y_min = np.min(arr, axis=0)
    return x_max, x_min, y_max, y_min
import numpy as np
def extrema_npat(arr):
    arr = arr.transpose()
    x_max, y_max = np.max(arr, axis=1)
    x_min, y_min = np.min(arr, axis=1)
    return x_max, x_min, y_max, y_min
def extrema_loop(arr):
    n, m = arr.shape
    x_min = x_max = arr[0, 0]
    y_min = y_max = arr[0, 1]
    for i in range(1, n):
        x, y = arr[i, :]
        if x > x_max:
            x_max = x
        elif x < x_min:
            x_min = x
        if y > y_max:
            y_max = y
        elif y < y_min:
            y_min = y
    return x_max, x_min, y_max, y_min
import numba as nb
@nb.jit(nopython=True)
def extrema_loop_nb(arr):
    n, m = arr.shape
    x_min = x_max = arr[0, 0]
    y_min = y_max = arr[0, 1]
    for i in range(1, n):
        x = arr[i, 0]
        y = arr[i, 1]
        if x > x_max:
            x_max = x
        elif x < x_min:
            x_min = x
        if y > y_max:
            y_max = y
        elif y < y_min:
            y_min = y
    return x_max, x_min, y_max, y_min
%%cython -c-O3 -c-march=native -a
#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True, infer_types=True
import numpy as np
import cython as cy
cdef void _extrema_loop_cy(
        long[:, :] arr,
        size_t n,
        size_t m,
        long[:, :] result):
    cdef size_t i, j
    cdef long x, y, x_max, x_min, y_max, y_min
    x_min = x_max = arr[0, 0]
    y_min = y_max = arr[0, 1]
    for i in range(1, n):
        x = arr[i, 0]
        y = arr[i, 1]
        if x > x_max:
            x_max = x
        elif x < x_min:
            x_min = x
        if y > y_max:
            y_max = y
        elif y < y_min:
            y_min = y
    result[0, 0] = x_max
    result[0, 1] = x_min
    result[1, 0] = y_max
    result[1, 1] = y_min
def extrema_loop_cy(arr):
    n, m = arr.shape
    result = np.zeros((2, m), dtype=arr.dtype)
    _extrema_loop_cy(arr, n, m, result)
    return result[0, 0], result[0, 1], result[1, 0], result[1, 1]
and their respective timings as a function of input size:

So, for NumPy array inputs, one can gets much faster timings.
The Numba- and Cython- based solution seems to be the fastest, remarkably outperforming the fastest NumPy-only approaches.
(full benchmarks available here)
(EDITED to improve Numba-based solution)