diff --git a/doc/source/index.rst b/doc/source/index.rst
index 94fd76232138995504434cc6119211b2db12250e..0f246c0a483993f5a4250e51615e3bb3abdc4935 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -6,11 +6,18 @@
 Welcome to PyCGTOOL's documentation!
 ====================================
 
-Contents:
+.. toctree::
+   :hidden:
+
+   self
+   tutorial
+   Module Documentation <modules>
+
 
 .. toctree::
    :maxdepth: 2
 
+
 Features
 --------
 PyCGTOOL provides a means to quickly and easily generate coarse-grained molecular dynamics models within the MARTINI framework from all-atom or united-atom simulation trajectories.
diff --git a/doc/source/pycgtool.rst b/doc/source/pycgtool.rst
index 3375064902240b17376bd9a0fa90bfffe6cd2e98..a61ef054c4e1ad21403f73b6d45e595c141a3207 100644
--- a/doc/source/pycgtool.rst
+++ b/doc/source/pycgtool.rst
@@ -18,6 +18,7 @@ Submodules
    pycgtool.frame
    pycgtool.interface
    pycgtool.mapping
+   pycgtool.pycgtool
    pycgtool.util
    pycgtool.warnings
 
diff --git a/pycgtool/bondset.py b/pycgtool/bondset.py
index 873d5bd7e854c557125963b3c46c4bb1d6da87c0..84494674c5c295b1b9e4f4acbd17aa998b55dd7d 100644
--- a/pycgtool/bondset.py
+++ b/pycgtool/bondset.py
@@ -8,8 +8,9 @@ import itertools
 
 import numpy as np
 import math
+import random
 
-from .util import stat_moments, sliding, dist_with_pbc
+from .util import stat_moments, sliding, dist_with_pbc, transpose_and_sample
 from .util import extend_graph_chain, cross, backup_file
 from .parsers.cfg import CFG
 
@@ -365,36 +366,23 @@ class BondSet:
 
         :param target_number: Approx number of sample measurements to output.  If None, all samples will be output
         """
-        def transpose(bond_list):
-            """
-            Transpose a list of bonds containing values and slice to provide target number of rows.
-            """
-            if not bond_list:
-                return []
-
-            lenmin = min([len(bond.values) for bond in bond_list])
-            rows = zip(*(bond.values for bond in bond_list))
-            if target_number is not None:
-                skip = 1 + max(0, lenmin // target_number)
-                rows = itertools.islice(rows, 0, None, skip)
-            return rows
 
         for mol in self._molecules:
             if mol == "SOL":
                 continue
             with open("{0}_length.dat".format(mol), "w") as f:
                 bonds = self.get_bond_lengths(mol, with_constr=True)
-                for row in transpose(bonds):
+                for row in transpose_and_sample((bond.values for bond in bonds), n=target_number):
                     print((len(row) * "{:12.5f}").format(*row), file=f)
 
             with open("{0}_angle.dat".format(mol), "w") as f:
                 bonds = self.get_bond_angles(mol)
-                for row in transpose(bonds):
+                for row in transpose_and_sample((bond.values for bond in bonds), n=target_number):
                     print((len(row) * "{:12.5f}").format(*row), file=f)
 
             with open("{0}_dihedral.dat".format(mol), "w") as f:
                 bonds = self.get_bond_dihedrals(mol)
-                for row in transpose(bonds):
+                for row in transpose_and_sample((bond.values for bond in bonds), n=target_number):
                     print((len(row) * "{:12.5f}").format(*row), file=f)
 
     def __len__(self):
diff --git a/pycgtool/util.py b/pycgtool/util.py
index 356942e8213e3e765457db93446ff41aea40fe61..62920591a6c26879ef061d91f2dfac955831ef47 100644
--- a/pycgtool/util.py
+++ b/pycgtool/util.py
@@ -4,6 +4,7 @@ This module contains some general purpose utility functions used in PyCGTOOL.
 
 import os
 import itertools
+import random
 
 import numpy as np
 np.seterr(all="raise")
@@ -138,6 +139,21 @@ def stat_moments(vals, ignore_nan=True):
         return np.zeros(2)
 
 
+def transpose_and_sample(sequence, n=None):
+    """
+    Transpose a sequence of lists and sample to provide target number of rows.
+
+    :param sequence: 2d sequence object to transpose
+    :param n: Number of samples to take
+    """
+    rows = list(zip(*sequence))
+
+    if n is not None and len(rows) > n:
+        rows = random.sample(rows, n)
+
+    return rows
+
+
 def dir_up(name, n=1):
     """
     Return the directory path n levels above a specified file/directory.
diff --git a/test/test_util.py b/test/test_util.py
index 23e8d2b5cf28a68c40a92aca3fd123751fc675e5..815403743c48edfd2c9139a89ec3df3db3b2479b 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -4,7 +4,7 @@ import os
 import numpy as np
 import numpy.testing
 
-from pycgtool.util import tuple_equivalent, extend_graph_chain, stat_moments
+from pycgtool.util import tuple_equivalent, extend_graph_chain, stat_moments, transpose_and_sample
 from pycgtool.util import dir_up, backup_file, sliding, r_squared, dist_with_pbc
 
 
@@ -90,6 +90,19 @@ class UtilTest(unittest.TestCase):
         fit = [i for i in range(1, 6)]
         self.assertEqual(0.5, r_squared(ref, fit))
 
+    def test_transpose_and_sample_no_sample(self):
+        l = [(1, 2), (3, 4), (5, 6)]
+        l_t = [(1, 3, 5), (2, 4, 6)]
+        self.assertEqual(l_t, transpose_and_sample(l, None))
+
+    def test_transpose_and_sample(self):
+        l = [(1, 2), (3, 4), (5, 6)]
+        l_t = [(1, 3, 5), (2, 4, 6)]
+
+        l_t_test = transpose_and_sample(l, n=1)
+        self.assertEqual(1, len(l_t_test))
+        self.assertIn(l_t_test[0], l_t)
+
 
 if __name__ == '__main__':
     unittest.main()