diff --git a/bilby/bilby_mcmc/chain.py b/bilby/bilby_mcmc/chain.py
index 8969d353623bd9570c30ca0829570dbc16f793e9..66d6c97f705690777be406303aafb320912b5416 100644
--- a/bilby/bilby_mcmc/chain.py
+++ b/bilby/bilby_mcmc/chain.py
@@ -1,7 +1,6 @@
-from distutils.version import LooseVersion
-
 import numpy as np
 import pandas as pd
+from packaging import version
 
 from ..core.sampler.base_sampler import SamplerError
 from ..core.utils import logger
@@ -512,7 +511,7 @@ class Sample(object):
 def calculate_tau(x, autocorr_c=5):
     import emcee
 
-    if LooseVersion(emcee.__version__) < LooseVersion("3"):
+    if version.parse(emcee.__version__) < version.parse("3"):
         raise SamplerError("bilby-mcmc requires emcee > 3.0 for autocorr analysis")
 
     if np.all(np.diff(x) == 0):
diff --git a/bilby/core/sampler/base_sampler.py b/bilby/core/sampler/base_sampler.py
index 380ca001267eca8f09e7a9d02c0a6520e11850b3..4568b165432418141f34c3f6b8e04b2d6f9ef41d 100644
--- a/bilby/core/sampler/base_sampler.py
+++ b/bilby/core/sampler/base_sampler.py
@@ -1,5 +1,4 @@
 import datetime
-import distutils.dir_util
 import os
 import shutil
 import signal
@@ -972,8 +971,10 @@ class _TemporaryFileSamplerMixin:
             f"Overwriting {self.outputfiles_basename} with {self.temporary_outputfiles_basename}"
         )
         outputfiles_basename_stripped = self.outputfiles_basename.rstrip("/")
-        distutils.dir_util.copy_tree(
-            self.temporary_outputfiles_basename, outputfiles_basename_stripped
+        shutil.copytree(
+            self.temporary_outputfiles_basename,
+            outputfiles_basename_stripped,
+            dirs_exist_ok=True,
         )
 
     def _setup_run_directory(self):
@@ -988,8 +989,10 @@ class _TemporaryFileSamplerMixin:
             self.temporary_outputfiles_basename = temporary_outputfiles_basename
 
             if os.path.exists(self.outputfiles_basename):
-                distutils.dir_util.copy_tree(
-                    self.outputfiles_basename, self.temporary_outputfiles_basename
+                shutil.copytree(
+                    self.outputfiles_basename,
+                    self.temporary_outputfiles_basename,
+                    dirs_exist_ok=True,
                 )
             check_directory_exists_and_if_not_mkdir(temporary_outputfiles_basename)
 
diff --git a/bilby/core/sampler/emcee.py b/bilby/core/sampler/emcee.py
index 112b56dd76187119e2b05b6bb13018fb13594193..6bb7fc7d7e31e847ebc65e5870a7e3d5f3e627dd 100644
--- a/bilby/core/sampler/emcee.py
+++ b/bilby/core/sampler/emcee.py
@@ -1,10 +1,10 @@
 import os
 import shutil
 from collections import namedtuple
-from distutils.version import LooseVersion
 from shutil import copyfile
 
 import numpy as np
+from packaging import version
 from pandas import DataFrame
 
 from ..utils import check_directory_exists_and_if_not_mkdir, logger, safe_file_dump
@@ -79,12 +79,7 @@ class Emcee(MCMCSampler):
         burn_in_act=3,
         **kwargs,
     ):
-        import emcee
-
-        if LooseVersion(emcee.__version__) > LooseVersion("2.2.1"):
-            self.prerelease = True
-        else:
-            self.prerelease = False
+        self._check_version()
         super(Emcee, self).__init__(
             likelihood=likelihood,
             priors=priors,
@@ -95,7 +90,6 @@ class Emcee(MCMCSampler):
             skip_import_verification=skip_import_verification,
             **kwargs,
         )
-        self._check_version()
         self.resume = resume
         self.pos0 = pos0
         self.nburn = nburn
@@ -106,11 +100,10 @@ class Emcee(MCMCSampler):
     def _check_version(self):
         import emcee
 
-        if LooseVersion(emcee.__version__) > LooseVersion("2.2.1"):
-            self.prerelease = True
-        else:
+        if version.parse(emcee.__version__) < version.parse("3"):
             self.prerelease = False
-        return emcee
+        else:
+            self.prerelease = True
 
     def _translate_kwargs(self, kwargs):
         kwargs = super()._translate_kwargs(kwargs)
diff --git a/bilby/core/sampler/pymc.py b/bilby/core/sampler/pymc.py
index feb703d3c653dc0b7e3301b260b79c33c243adeb..e72aace4948c8b46e6b4d68e74940d378f42ff99 100644
--- a/bilby/core/sampler/pymc.py
+++ b/bilby/core/sampler/pymc.py
@@ -1,5 +1,3 @@
-from distutils.version import StrictVersion
-
 import numpy as np
 
 from ...gw.likelihood import BasicGravitationalWaveTransient, GravitationalWaveTransient
@@ -631,23 +629,13 @@ class Pymc(MCMCSampler):
                         self.kwargs["step"] = pymc.__dict__[step_methods[curmethod]](
                             **args
                         )
-                    else:
-                        # re-add step_kwargs if no step methods are set
-                        if len(step_kwargs) > 0 and StrictVersion(
-                            pymc.__version__
-                        ) < StrictVersion("3.7"):
-                            self.kwargs["step_kwargs"] = step_kwargs
 
         # check whether only NUTS step method has been assigned
         if np.all([sm.lower() == "nuts" for sm in methodslist]):
             # in this case we can let PyMC autoinitialise NUTS, so remove the step methods and re-add nuts_kwargs
             self.kwargs["step"] = None
 
-            if len(nuts_kwargs) > 0 and StrictVersion(pymc.__version__) < StrictVersion(
-                "3.7"
-            ):
-                self.kwargs["nuts_kwargs"] = nuts_kwargs
-            elif len(nuts_kwargs) > 0:
+            if len(nuts_kwargs) > 0:
                 # add NUTS kwargs to standard kwargs
                 self.kwargs.update(nuts_kwargs)
 
@@ -707,10 +695,6 @@ class Pymc(MCMCSampler):
             args = {}
         return args, nuts_kwargs
 
-    def _pymc_version(self):
-        pymc, _, _ = self._import_external_sampler()
-        return pymc.__version__
-
     def set_prior(self):
         """
         Set the PyMC prior distributions.
diff --git a/bilby/core/utils/plotting.py b/bilby/core/utils/plotting.py
index c41148047193de4bc3d475de592e498aaed8675e..02d14944783c5d7622f3d6223b59098ce675ecd6 100644
--- a/bilby/core/utils/plotting.py
+++ b/bilby/core/utils/plotting.py
@@ -1,6 +1,6 @@
 import functools
 import os
-from distutils.spawn import find_executable
+import shutil
 
 from .log import logger
 
@@ -53,7 +53,7 @@ def latex_plot_format(func):
             _old_tex = rcParams["text.usetex"]
             _old_serif = rcParams["font.serif"]
             _old_family = rcParams["font.family"]
-            if find_executable("latex"):
+            if shutil.which("latex"):
                 rcParams["text.usetex"] = True
             else:
                 rcParams["text.usetex"] = False